2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-05-16 23:11:42 +00:00

feat: add support for extracting ipaks to ImageConverter

This commit is contained in:
Jan Laupetin
2026-05-11 21:58:25 +02:00
parent a2ab44da39
commit 36b4ec7781
6 changed files with 98 additions and 60 deletions
+2
View File
@@ -39,12 +39,14 @@ function ImageConverter:project()
self:include(includes)
Utils:include(includes)
Common:include(includes)
ObjLoading:include(includes)
ObjImage:include(includes)
Raw:use()
links:linkto(Utils)
links:linkto(Common)
links:linkto(ObjLoading)
links:linkto(ObjImage)
links:linkall()
end
+79 -27
View File
@@ -8,8 +8,8 @@
#include "Image/IwiWriter27.h"
#include "Image/IwiWriter6.h"
#include "Image/IwiWriter8.h"
#include "Image/Texture.h"
#include "ImageConverterArgs.h"
#include "ObjContainer/IPak/IPak.h"
#include "Utils/Logging/Log.h"
#include "Utils/StringUtils.h"
@@ -27,36 +27,17 @@ namespace
{
constexpr auto EXTENSION_IWI = ".iwi";
constexpr auto EXTENSION_DDS = ".dds";
constexpr auto EXTENSION_IPAK = ".ipak";
class ImageConverterImpl final : public ImageConverter
class ImageConverter
{
public:
ImageConverterImpl()
: m_game_to_convert_to(std::nullopt)
explicit ImageConverter(const ImageConverterArgs& args)
: m_game_to_convert_to(args.m_game_to_convert_to)
{
}
bool Start(const int argc, const char** argv) override
{
con::init();
auto shouldContinue = true;
if (!m_args.ParseArgs(argc, argv, shouldContinue))
return false;
if (!shouldContinue)
return true;
m_game_to_convert_to = m_args.m_game_to_convert_to;
for (const auto& file : m_args.m_files_to_convert)
Convert(file);
return true;
}
private:
void Convert(const std::string& file)
void HandleFile(const std::string& file)
{
const fs::path filePath(file);
auto extension = filePath.extension().string();
@@ -66,10 +47,13 @@ namespace
ConvertIwi(filePath);
else if (extension == EXTENSION_DDS)
ConvertDds(filePath);
else if (extension == EXTENSION_IPAK)
ExtractIpak(filePath);
else
con::error("Unsupported extension {}", extension);
}
private:
bool ConvertIwi(const fs::path& iwiPath)
{
std::ifstream file(iwiPath, std::ios::in | std::ios::binary);
@@ -195,6 +179,60 @@ namespace
return true;
}
bool ExtractIpak(const fs::path& ipakPath)
{
auto file = std::make_unique<std::ifstream>(ipakPath, std::ios::in | std::ios::binary);
if (!file->is_open())
{
con::error("Failed to open ipak {}", ipakPath.string());
return false;
}
auto ipak = IIPak::Create(ipakPath.string(), std::move(file));
if (!ipak->Initialize())
{
con::error("Failed to read ipak {}", ipakPath.string());
return false;
}
const auto outDir = fs::absolute(ipakPath).parent_path() / ipakPath.filename().replace_extension();
fs::create_directories(outDir);
for (const auto& indexEntry : ipak->GetIndexEntries())
{
const auto fileName = std::format("{:6x}_{:6x}.iwi", indexEntry.key.dataHash, indexEntry.key.nameHash);
std::ofstream outFile(outDir / fileName, std::ios::out | std::ios::binary);
if (!outFile.is_open())
{
con::error("Failed to open ipak file {}", fileName);
return false;
}
auto entryStream = ipak->GetEntryStream(indexEntry.key.nameHash, indexEntry.key.dataHash);
if (!entryStream)
{
con::error("Failed to open entry stream for {}", fileName);
return false;
}
char buffer[0x2000];
entryStream->read(buffer, 0x2000);
auto readCount = entryStream->gcount();
while (readCount > 0)
{
outFile.write(buffer, readCount);
entryStream->read(buffer, 0x2000);
readCount = entryStream->gcount();
}
entryStream->close();
outFile.close();
}
return true;
}
ImageConverterArgs m_args;
std::optional<GameId> m_game_to_convert_to;
DdsWriter m_dds_writer;
@@ -202,7 +240,21 @@ namespace
};
} // namespace
std::unique_ptr<ImageConverter> ImageConverter::Create()
bool RunImageConverter(const int argc, const char** argv)
{
return std::make_unique<ImageConverterImpl>();
con::init();
auto shouldContinue = true;
ImageConverterArgs args;
if (!args.ParseArgs(argc, argv, shouldContinue))
return false;
if (!shouldContinue)
return true;
ImageConverter imageConverter(args);
for (const auto& file : args.m_files_to_convert)
imageConverter.HandleFile(file);
return true;
}
+1 -22
View File
@@ -1,24 +1,3 @@
#pragma once
#include <memory>
class ImageConverter
{
public:
ImageConverter() = default;
virtual ~ImageConverter() = default;
ImageConverter(const ImageConverter& other) = delete;
ImageConverter(ImageConverter&& other) noexcept = delete;
ImageConverter& operator=(const ImageConverter& other) = delete;
ImageConverter& operator=(ImageConverter&& other) noexcept = delete;
/**
* \brief Starts the ImageConverter application logic.
* \param argc The amount of command line arguments specified.
* \param argv The command line arguments.
* \return \c true if the application was successful or \c false if an error occurred.
*/
virtual bool Start(int argc, const char** argv) = 0;
static std::unique_ptr<ImageConverter> Create();
};
bool RunImageConverter(int argc, const char** argv);
+1 -3
View File
@@ -2,7 +2,5 @@
int main(const int argc, const char** argv)
{
const auto imageConverter = ImageConverter::Create();
return imageConverter->Start(argc, argv) ? 0 : 1;
return RunImageConverter(argc, argv) ? 0 : 1;
}
+8 -3
View File
@@ -55,7 +55,7 @@ namespace
return true;
}
[[nodiscard]] std::unique_ptr<iobjstream> GetEntryStream(const Hash nameHash, const Hash dataHash) const override
[[nodiscard]] std::unique_ptr<iobjstream> GetEntryStream(const IPakHash nameHash, const IPakHash dataHash) const override
{
const IPakIndexEntryKey wantedKey{
{.dataHash = dataHash, .nameHash = nameHash}
@@ -77,6 +77,11 @@ namespace
return nullptr;
}
[[nodiscard]] const std::vector<IPakIndexEntry>& GetIndexEntries() const override
{
return m_index_entries;
}
std::string GetName() override
{
return fs::path(m_path).filename().replace_extension("").string();
@@ -203,12 +208,12 @@ std::unique_ptr<IIPak> IIPak::Create(std::string path, std::unique_ptr<std::istr
return std::make_unique<IPak>(std::move(path), std::move(stream));
}
IIPak::Hash IIPak::HashString(const std::string& str)
IPakHash IIPak::HashString(const std::string& str)
{
return R_HashString(str.c_str(), 0);
}
IIPak::Hash IIPak::HashData(const void* data, const size_t dataSize)
IPakHash IIPak::HashData(const void* data, const size_t dataSize)
{
return crc32(0, static_cast<const Bytef*>(data), static_cast<unsigned>(dataSize));
}
+7 -5
View File
@@ -1,5 +1,6 @@
#pragma once
#include "ObjContainer/IPak/IPakTypes.h"
#include "ObjContainer/ObjContainerReferenceable.h"
#include "ObjContainer/ObjContainerRepository.h"
#include "Utils/ObjStream.h"
@@ -14,19 +15,20 @@ class IIPak : public ObjContainerReferenceable
{
public:
static ObjContainerRepository<IIPak, Zone> Repository;
typedef std::uint32_t Hash;
IIPak() = default;
virtual ~IIPak() = default;
~IIPak() override = default;
IIPak(const IIPak& other) = default;
IIPak(IIPak&& other) noexcept = default;
IIPak& operator=(const IIPak& other) = default;
IIPak& operator=(IIPak&& other) noexcept = default;
virtual bool Initialize() = 0;
[[nodiscard]] virtual std::unique_ptr<iobjstream> GetEntryStream(Hash nameHash, Hash dataHash) const = 0;
[[nodiscard]] virtual std::unique_ptr<iobjstream> GetEntryStream(IPakHash nameHash, IPakHash dataHash) const = 0;
[[nodiscard]] virtual const std::vector<IPakIndexEntry>& GetIndexEntries() const = 0;
static std::unique_ptr<IIPak> Create(std::string path, std::unique_ptr<std::istream> stream);
static Hash HashString(const std::string& str);
static Hash HashData(const void* data, size_t dataSize);
static IPakHash HashString(const std::string& str);
static IPakHash HashData(const void* data, size_t dataSize);
};