Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions FileFormats/Erf/Erf_Friendly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

namespace FileFormats::Erf::Friendly {

Erf::Erf()
{ }

Erf::Erf(Raw::Erf const& rawErf)
{
ConstructInternal(rawErf);
Expand All @@ -16,16 +19,106 @@ Erf::Erf(Raw::Erf&& rawErf) : m_RawErf(std::forward<Raw::Erf>(rawErf))
ConstructInternal(m_RawErf.value());
}

std::vector<Raw::ErfLocalisedString>& Erf::GetDescriptions()
{
return m_Descriptions;
}

std::vector<Raw::ErfLocalisedString> const& Erf::GetDescriptions() const
{
return m_Descriptions;
}

std::vector<ErfResource>& Erf::GetResources()
{
return m_Resources;
}

std::vector<ErfResource> const& Erf::GetResources() const
{
return m_Resources;
}

char* Erf::GetFileType()
{
return m_FileType;
}

const char* Erf::GetFileType() const
{
return m_FileType;
}

bool Erf::WriteToFile(char const* path) const
{
Raw::Erf raw_erf;

std::memcpy(raw_erf.m_Header.m_FileType, m_FileType, 4);
std::memcpy(raw_erf.m_Header.m_Version, "V1.0", 4);

raw_erf.m_Header.m_LanguageCount = (std::uint32_t)m_Descriptions.size();
raw_erf.m_Header.m_EntryCount = (std::uint32_t)m_Resources.size();

raw_erf.m_Header.m_LocalizedStringSize = 0;
for (const Raw::ErfLocalisedString& str : m_Descriptions)
{
raw_erf.m_Header.m_LocalizedStringSize += sizeof(str.m_LanguageId)
+ sizeof(std::uint32_t) // string size
+ (std::uint32_t)str.m_String.size(); // string
}

std::uint32_t offset_str_table = sizeof(raw_erf.m_Header);
std::uint32_t offset_keys = offset_str_table + raw_erf.m_Header.m_LocalizedStringSize;
std::uint32_t offset_resources = offset_keys + (raw_erf.m_Header.m_EntryCount * sizeof(Raw::ErfKey));
std::uint32_t offset_to_res_data = offset_resources + (raw_erf.m_Header.m_EntryCount * sizeof(Raw::ErfResource));

raw_erf.m_Header.m_OffsetToLocalizedString = offset_str_table;
raw_erf.m_Header.m_OffsetToKeyList = offset_keys;
raw_erf.m_Header.m_OffsetToResourceList = offset_resources;
raw_erf.m_Header.m_BuildYear = 666;
raw_erf.m_Header.m_BuildDay = 333;
raw_erf.m_Header.m_DescriptionStrRef = ~0u;
std::memset(raw_erf.m_Header.m_Reserved, 0, sizeof(raw_erf.m_Header.m_Reserved));

raw_erf.m_LocalisedStrings = m_Descriptions;

{
uint32_t total_data_size = 0;

for (const ErfResource& friendly_res : m_Resources)
{
total_data_size += (std::uint32_t)friendly_res.m_DataBlock->GetDataLength();
}

std::unique_ptr<OwningDataBlock> data = std::make_unique<OwningDataBlock>();
data->m_Data.resize(total_data_size);
raw_erf.m_ResourceData = std::move(data);
}

for (std::uint32_t i = 0, offset_in_res_data = 0; i < raw_erf.m_Header.m_EntryCount; ++i)
{
const ErfResource& friendly_res = m_Resources[i];

Raw::ErfKey key;
std::memset(key.m_ResRef, 0, sizeof(key.m_ResRef));
std::memcpy(key.m_ResRef, friendly_res.m_ResRef.c_str(), friendly_res.m_ResRef.size());
key.m_ResId = i;
key.m_ResType = friendly_res.m_ResType;
std::memset(key.m_Reserved, 0, sizeof(key.m_Reserved));
raw_erf.m_Keys.emplace_back(std::move(key));

Raw::ErfResource res;
res.m_OffsetToResource = offset_to_res_data + offset_in_res_data;
res.m_ResourceSize = (std::uint32_t)friendly_res.m_DataBlock->GetDataLength();
raw_erf.m_Resources.emplace_back(std::move(res));

std::memcpy((void*)(raw_erf.m_ResourceData->GetData() + offset_in_res_data), friendly_res.m_DataBlock->GetData(), res.m_ResourceSize);
offset_in_res_data += res.m_ResourceSize;
}

return raw_erf.WriteToFile(path);
}

void Erf::ConstructInternal(Raw::Erf const& rawErf)
{
// First - copy in the descriptions. This one is simple.
Expand Down
12 changes: 12 additions & 0 deletions FileFormats/Erf/Erf_Friendly.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ struct ErfResource
class Erf
{
public:
Erf();

// This constructs a friendly Erf from a raw Erf.
Erf(Raw::Erf const& rawBif);

Expand All @@ -33,9 +35,17 @@ class Erf
// the memory usage significantly.
Erf(Raw::Erf&& rawBif);

std::vector<Raw::ErfLocalisedString>& GetDescriptions();
std::vector<Raw::ErfLocalisedString> const& GetDescriptions() const;

std::vector<ErfResource>& GetResources();
std::vector<ErfResource> const& GetResources() const;

char* GetFileType();
const char* GetFileType() const;

bool WriteToFile(char const* path) const;

private:
std::optional<Raw::Erf> m_RawErf;

Expand All @@ -46,6 +56,8 @@ class Erf

// A vector of resources contained within this ERF.
std::vector<ErfResource> m_Resources;

char m_FileType[4];

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change this (and the other one) to std:array<char, 4>?

};

}
28 changes: 28 additions & 0 deletions FileFormats/Erf/Erf_Raw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,34 @@ bool Erf::ReadFromFile(char const* path, Erf* out)
return true;
}

bool Erf::WriteToFile(char const* path) const
{
ASSERT(path);

FILE* outFile = std::fopen(path, "wb");

if (outFile)
{
std::fwrite(&m_Header, sizeof(m_Header), 1, outFile);

for (const ErfLocalisedString& str : m_LocalisedStrings)
{
std::fwrite(&str.m_LanguageId, sizeof(str.m_LanguageId), 1, outFile);
uint32_t len = (std::uint32_t)str.m_String.size();
std::fwrite(&len, sizeof(len), 1, outFile);
std::fwrite(str.m_String.c_str(), len, 1, outFile);
}

std::fwrite(m_Keys.data(), m_Keys.size() * sizeof(ErfKey), 1, outFile);
std::fwrite(m_Resources.data(), m_Resources.size() * sizeof(ErfResource), 1, outFile);
std::fwrite(m_ResourceData->GetData(), m_ResourceData->GetDataLength(), 1, outFile);

std::fclose(outFile);
return true;
}

return false;
}

bool Erf::ConstructInternal(std::byte const* bytes)
{
Expand Down
4 changes: 3 additions & 1 deletion FileFormats/Erf/Erf_Raw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ struct Erf
// Constructs an Erf from a file. The file with be memory mapped so memory usage will be ideal.
static bool ReadFromFile(char const* path, Erf* out);

// Writes the raw ERF to disk.
bool WriteToFile(char const* path) const;

private:

// This is an RAII wrapper around the various methods of loading a BIF that we have.
Expand All @@ -133,7 +136,6 @@ struct Erf
void ReadLocalisedStrings(std::byte const* data);
void ReadKeys(std::byte const* data);
void ReadResources(std::byte const* data);
void ReadResourceData(std::byte const* data, std::size_t bytesCount);
};

}
22 changes: 17 additions & 5 deletions FileFormats/Gff/Gff_Friendly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ Gff::Gff() : m_TopLevelStruct()
{ }

Gff::Gff(Raw::Gff const& rawGff) : m_TopLevelStruct(rawGff.m_Structs[0], rawGff)
{ }
{
memcpy(m_FileType, rawGff.m_Header.m_FileType, sizeof(m_FileType));
}

GffStruct& Gff::GetTopLevelStruct()
{
Expand All @@ -225,10 +227,20 @@ GffStruct const& Gff::GetTopLevelStruct() const
return m_TopLevelStruct;
}

char* Gff::GetFileType()
{
return m_FileType;
}

const char* Gff::GetFileType() const
{
return m_FileType;
}

struct GffCreator
{
public:
std::unique_ptr<Raw::Gff> Create(const GffStruct& topLevelStruct);
std::unique_ptr<Raw::Gff> Create(const GffStruct& topLevelStruct, const char* fleType);

private:
template <typename T>
Expand Down Expand Up @@ -262,16 +274,16 @@ struct GffCreator

bool Gff::WriteToFile(char const* path) const
{
return GffCreator().Create(m_TopLevelStruct)->WriteToFile(path);
return GffCreator().Create(m_TopLevelStruct, m_FileType)->WriteToFile(path);
}

std::unique_ptr<Raw::Gff> GffCreator::Create(const GffStruct& topLevelStruct)
std::unique_ptr<Raw::Gff> GffCreator::Create(const GffStruct& topLevelStruct, const char* fileType)
{
m_RawGff = std::make_unique<Raw::Gff>();
InsertIntoRawGff(topLevelStruct);

Raw::GffHeader* header = &m_RawGff->m_Header;
std::memcpy(header->m_FileType, "UTC ", 4);
std::memcpy(header->m_FileType, fileType, 4);
std::memcpy(header->m_FileVersion, "V3.2", 4);

header->m_StructOffset = sizeof(Raw::GffHeader);
Expand Down
4 changes: 4 additions & 0 deletions FileFormats/Gff/Gff_Friendly.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,14 @@ class Gff
GffStruct& GetTopLevelStruct();
GffStruct const& GetTopLevelStruct() const;

char* GetFileType();
const char* GetFileType() const;

bool WriteToFile(char const* path) const;

private:
GffStruct m_TopLevelStruct;
char m_FileType[4];
};

}
1 change: 1 addition & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ There are some tools in the Tools subdirectory:
- generate_placeable_blueprints allows the user to generate a series of blueprints from placeables defined in 2da using a base blueprint
- key_bif_extractor allows extracting all resources in a KEY from their BIFs.
- erf_extractor allows extracting all resources from an ERF.
- erf_packer allows you to pack resources into an ERF (like nwhak).
8 changes: 8 additions & 0 deletions Tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ add_executable(erf_extractor Tool_ErfExtractor.cpp)
target_link_libraries(erf_extractor FileFormats)
set_target_properties(erf_extractor PROPERTIES FOLDER "Tools")

add_executable(erf_packer Tool_ErfPacker.cpp)
target_link_libraries(erf_packer FileFormats)
set_target_properties(erf_packer PROPERTIES FOLDER "Tools")

if (UNIX)
target_link_libraries(erf_packer stdc++fs)
endif()

add_executable(key_bif_extractor Tool_KeyBifExtractor.cpp)
target_link_libraries(key_bif_extractor FileFormats)
set_target_properties(key_bif_extractor PROPERTIES FOLDER "Tools")
Expand Down
77 changes: 77 additions & 0 deletions Tools/Tool_ErfPacker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "FileFormats/Erf.hpp"
#include "Utility/Assert.hpp"

#include <algorithm>
#include <cstring>
#include <filesystem>
#include <vector>

namespace
{

int pack_erf(const char* out_path, const std::vector<const char*>& files)
{
using namespace FileFormats::Erf;

Friendly::Erf erf;

Raw::ErfLocalisedString desc;
desc.m_LanguageId = 0;
desc.m_String = "ERFPacked\nhttps://github.com/Liareth/NWNFileFormats\nThis ERF was packed with the pack_erf tool from the NWNFileFormats library.";
erf.GetDescriptions().emplace_back(std::move(desc));

for (const char* file : files)
{
std::filesystem::path file_path(file);

std::uintmax_t len = std::filesystem::file_size(file_path);
std::unique_ptr<OwningDataBlock> db = std::make_unique<OwningDataBlock>();
db->m_Data.resize(len);

FILE* f = std::fopen(file, "rb");
ASSERT(f);

if (f)
{
std::fread(db->m_Data.data(), len, 1, f);
std::fclose(f);
}
else
{
continue;
}

Friendly::ErfResource res;
res.m_ResRef = file_path.stem().string();
res.m_ResType = FileFormats::Resource::ResourceTypeFromString(file_path.extension().string().substr(1).c_str());
res.m_DataBlock = std::move(db);
erf.GetResources().emplace_back(std::move(res));
}

std::string ext = std::filesystem::path(out_path).extension().string().substr(1);
std::transform(std::begin(ext), std::end(ext), std::begin(ext), ::toupper);
std::memcpy(erf.GetFileType(), ext.c_str(), 3);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could we add a function to Resource.hpp to map between resource type and file type? Like: std::array<char, 4> FiletypeFromResource(ResourceType res).

erf.GetFileType()[3] = ' ';

return !erf.WriteToFile(out_path);
}

}

int main(int argc, char** argv)
{
if (argc < 3)
{
std::printf("erf_extractor [out_file] [files...]\n");
return 1;
}

std::vector<const char*> paths;

for (int i = 2; i < argc; ++i)
{
paths.emplace_back(argv[i]);
}

return pack_erf(argv[1], paths);
}