Add WIP YATF exporter
Doesn't work for *everything* but works for a good chunk of stuff
This commit is contained in:
parent
7fd4c470af
commit
1782fef0dc
|
@ -9,5 +9,7 @@ include(cmake/Policies.cmake)
|
||||||
|
|
||||||
project(EuropaTools)
|
project(EuropaTools)
|
||||||
|
|
||||||
|
add_subdirectory(third_party/libpixel)
|
||||||
|
|
||||||
add_subdirectory(src/libeuropa)
|
add_subdirectory(src/libeuropa)
|
||||||
add_subdirectory(src/tools)
|
add_subdirectory(src/tools)
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef EUROPA_IO_YATFREADER_H
|
||||||
|
#define EUROPA_IO_YATFREADER_H
|
||||||
|
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <europa/structs/Yatf.h>
|
||||||
|
#include <pixel/RgbaImage.h>
|
||||||
|
|
||||||
|
namespace europa::io {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader for PS2 Europa .tex files.
|
||||||
|
*/
|
||||||
|
struct YatfReader {
|
||||||
|
explicit YatfReader(std::istream& is);
|
||||||
|
|
||||||
|
void Init(std::istream& is);
|
||||||
|
|
||||||
|
void ReadImage();
|
||||||
|
|
||||||
|
pixel::RgbaImage& GetImage();
|
||||||
|
|
||||||
|
const structs::YatfHeader& GetHeader() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool Invalid() const {
|
||||||
|
return invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::istream& stream;
|
||||||
|
bool invalid {false};
|
||||||
|
|
||||||
|
|
||||||
|
structs::YatfHeader header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converted image.
|
||||||
|
*/
|
||||||
|
pixel::RgbaImage image;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EUROPA_IO_YATFREADER_H
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef EUROPA_STRUCTS_YATF_H
|
||||||
|
#define EUROPA_STRUCTS_YATF_H
|
||||||
|
|
||||||
|
#include <europa/util/FourCC.h>
|
||||||
|
|
||||||
|
#include <europa/structs/ImHexAdapter.h>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace europa::structs {
|
||||||
|
|
||||||
|
struct [[gnu::packed]] YatfHeader {
|
||||||
|
|
||||||
|
constexpr static u32 TextureFlag_Unknown = 0x1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture does not have a palette
|
||||||
|
*/
|
||||||
|
constexpr static u32 TextureFlag_NoPalette = 0x30000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture uses alpha.
|
||||||
|
*/
|
||||||
|
constexpr static u32 TextureFlag_UsesAlpha = 0x1000000;
|
||||||
|
|
||||||
|
constexpr static auto ValidMagic = util::FourCC<"YATF", std::endian::big>();
|
||||||
|
|
||||||
|
u32 magic;
|
||||||
|
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
// Always zeroed.
|
||||||
|
u32 zero;
|
||||||
|
|
||||||
|
u32 height;
|
||||||
|
u32 width;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool IsValid() const {
|
||||||
|
return magic == ValidMagic;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EUROPA_STRUCTS_YATF_H
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// SSX 3 Lobby Server
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// This file is licensed under the GNU General Public License Version 3.
|
||||||
|
// Text is provided in LICENSE.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef EUROPA_UTIL_FIXEDSTRING_H
|
||||||
|
#define EUROPA_UTIL_FIXEDSTRING_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace europa::util {
|
||||||
|
|
||||||
|
template<std::size_t N>
|
||||||
|
struct FixedString {
|
||||||
|
char buf[N + 1]{};
|
||||||
|
|
||||||
|
constexpr FixedString(const char* s) { // NOLINT
|
||||||
|
for (unsigned i = 0; i != N; ++i)
|
||||||
|
buf[i] = s[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr operator const char*() const { // NOLINT
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::size_t Length() const {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<std::size_t N>
|
||||||
|
FixedString(char const (&)[N]) -> FixedString<N - 1>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EUROPA_UTIL_FIXEDSTRING_H
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// SSX 3 Lobby Server
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// This file is licensed under the GNU General Public License Version 3.
|
||||||
|
// Text is provided in LICENSE.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef EUROPA_FOURCC_H
|
||||||
|
#define EUROPA_FOURCC_H
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
|
||||||
|
#include <europa/util/FixedString.h>
|
||||||
|
|
||||||
|
namespace europa::util {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A multi-endian, compile-time FourCC generator.
|
||||||
|
* You love to see it.
|
||||||
|
*/
|
||||||
|
template<FixedString fccString, std::endian Endian = std::endian::little>
|
||||||
|
consteval std::uint32_t FourCC() {
|
||||||
|
static_assert(fccString.Length() == 4, "Provided string is not a FourCC");
|
||||||
|
|
||||||
|
switch(Endian) {
|
||||||
|
case std::endian::little:
|
||||||
|
return (fccString[0]) | (fccString[1] << 8) | (fccString[2] << 16) | (fccString[3] << 24);
|
||||||
|
|
||||||
|
case std::endian::big:
|
||||||
|
return (fccString[0] << 24) | (fccString[1] << 16) | (fccString[2] << 8) | fccString[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just return something with all possible bits set, if the user somehow
|
||||||
|
// got around the above switch (which shouldn't happen).
|
||||||
|
return 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EUROPA_FOURCC_H
|
|
@ -1,7 +1,9 @@
|
||||||
|
|
||||||
|
|
||||||
add_library(libeuropa
|
add_library(libeuropa
|
||||||
|
io/StreamUtils.cpp
|
||||||
io/PakReader.cpp
|
io/PakReader.cpp
|
||||||
|
io/YatfReader.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(libeuropa PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
target_include_directories(libeuropa PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||||
|
@ -10,3 +12,9 @@ set_target_properties(libeuropa PROPERTIES
|
||||||
CXX_STANDARD 20
|
CXX_STANDARD 20
|
||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Projects which libeuropa depends on
|
||||||
|
target_link_libraries(libeuropa PUBLIC
|
||||||
|
|
||||||
|
pixel::libpixel
|
||||||
|
)
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace europa::io {
|
||||||
|
|
||||||
void PakReader::ReadData() {
|
void PakReader::ReadData() {
|
||||||
auto ReadHeader = [&]() {
|
auto ReadHeader = [&]() {
|
||||||
header = LameRead<structs::PakHeader>(stream);
|
header = impl::ReadStreamType<structs::PakHeader>(stream);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ReadTocEntry = [&]() {
|
auto ReadTocEntry = [&]() {
|
||||||
|
@ -29,10 +29,10 @@ namespace europa::io {
|
||||||
// which we don't store inside the type (because we can't)
|
// which we don't store inside the type (because we can't)
|
||||||
//
|
//
|
||||||
// Read this in first.
|
// Read this in first.
|
||||||
auto filename = ReadPString(stream);
|
auto filename = impl::ReadPString(stream);
|
||||||
|
|
||||||
// Then read in the rest.
|
// Then read in the rest.
|
||||||
tocData[filename] = LameRead<structs::PakTocEntry>(stream);
|
tocData[filename] = impl::ReadStreamType<structs::PakTocEntry>(stream);
|
||||||
};
|
};
|
||||||
|
|
||||||
ReadHeader();
|
ReadHeader();
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "StreamUtils.h"
|
||||||
|
|
||||||
|
namespace europa::io::impl {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
void ReadStreamTypeImpl(std::istream& is, char* buffer, std::size_t size) {
|
||||||
|
if(!is)
|
||||||
|
throw std::runtime_error("stream is bad");
|
||||||
|
|
||||||
|
is.read(&buffer[0], size);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
std::string ReadZeroTerminatedString(std::istream& is) {
|
||||||
|
std::string s;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
if(!is)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
c = static_cast<char>(is.get());
|
||||||
|
|
||||||
|
if(c == '\0')
|
||||||
|
return s;
|
||||||
|
|
||||||
|
s.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadPString(std::istream& is) {
|
||||||
|
std::string s;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
if(!is)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// should be just resizing, and refactor this loop to not do this,
|
||||||
|
// but .... meh. I'll get to it if it's a problem
|
||||||
|
std::uint8_t length = is.get();
|
||||||
|
s.reserve(length);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
c = static_cast<char>(is.get());
|
||||||
|
|
||||||
|
if(c == '\0')
|
||||||
|
return s;
|
||||||
|
|
||||||
|
s.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace europa::io::impl
|
|
@ -12,59 +12,34 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace europa::io {
|
namespace europa::io::impl {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
void ReadStreamTypeImpl(std::istream& is, char* buffer, std::size_t size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// This is lame. But it works :)
|
// This is lame. But it works :)
|
||||||
template <class T>
|
template <class T>
|
||||||
T LameRead(std::istream& is) {
|
constexpr T ReadStreamType(std::istream& is) {
|
||||||
if(!is)
|
T object {};
|
||||||
throw std::runtime_error("stream is bad");
|
|
||||||
|
|
||||||
T t {};
|
// Absolutely UB.
|
||||||
is.read(reinterpret_cast<char*>(&t), sizeof(T));
|
union Hack {
|
||||||
return t;
|
T* t;
|
||||||
|
char* c;
|
||||||
|
} address {
|
||||||
|
.t = &object
|
||||||
|
};
|
||||||
|
|
||||||
|
detail::ReadStreamTypeImpl(is, address.c, sizeof(T));
|
||||||
|
|
||||||
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ReadZeroTerminatedString(std::istream& is) {
|
std::string ReadZeroTerminatedString(std::istream& is);
|
||||||
std::string s;
|
std::string ReadPString(std::istream& is);
|
||||||
char c;
|
|
||||||
|
|
||||||
if(!is)
|
} // namespace europa::io::impl
|
||||||
return "";
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
c = static_cast<char>(is.get());
|
|
||||||
|
|
||||||
if(c == '\0')
|
|
||||||
return s;
|
|
||||||
|
|
||||||
s.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReadPString(std::istream& is) {
|
|
||||||
std::string s;
|
|
||||||
char c;
|
|
||||||
|
|
||||||
if(!is)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// should be just resizing, and refactor this loop to not do this,
|
|
||||||
// but .... meh. I'll get to it if it's a problem
|
|
||||||
std::uint8_t length = is.get();
|
|
||||||
s.reserve(length);
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
c = static_cast<char>(is.get());
|
|
||||||
|
|
||||||
if(c == '\0')
|
|
||||||
return s;
|
|
||||||
|
|
||||||
s.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // EUROPA_TOOLS_STREAMUTILS_H
|
#endif // EUROPA_TOOLS_STREAMUTILS_H
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <europa/io/YatfReader.h>
|
||||||
|
#include "StreamUtils.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace europa::io {
|
||||||
|
|
||||||
|
YatfReader::YatfReader(std::istream& is)
|
||||||
|
: stream(is) {
|
||||||
|
Init(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void YatfReader::Init(std::istream& is) {
|
||||||
|
// Read the image header.
|
||||||
|
header = impl::ReadStreamType<structs::YatfHeader>(stream);
|
||||||
|
|
||||||
|
if(!header.IsValid())
|
||||||
|
invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void YatfReader::ReadImage() {
|
||||||
|
if(header.flags & structs::YatfHeader::TextureFlag_NoPalette) {
|
||||||
|
image.Resize({ static_cast<std::uint16_t>(header.width), static_cast<std::uint16_t>(header.height) });
|
||||||
|
|
||||||
|
stream.read(reinterpret_cast<char*>(image.GetBuffer()), (header.width * header.height) * sizeof(pixel::RgbaColor));
|
||||||
|
} else {
|
||||||
|
pixel::RgbaColor palette[256];
|
||||||
|
std::vector<std::uint8_t> tempBuffer((header.width * header.height));
|
||||||
|
|
||||||
|
// NB: sizeof() does pre-multiplication, so it's 100% ok for us to do this.
|
||||||
|
stream.read(reinterpret_cast<char*>(&palette[0]), sizeof(palette));
|
||||||
|
|
||||||
|
stream.read(reinterpret_cast<char*>(&tempBuffer[0]), tempBuffer.size());
|
||||||
|
|
||||||
|
image.Resize({ static_cast<std::uint16_t>(header.width), static_cast<std::uint16_t>(header.height) });
|
||||||
|
|
||||||
|
auto* buffer = image.GetBuffer();
|
||||||
|
const auto* data = &tempBuffer[0];
|
||||||
|
|
||||||
|
for(std::size_t i = 0; i < header.width * header.height; ++i)
|
||||||
|
*(buffer++) = palette[data[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixel::RgbaImage& YatfReader::GetImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
const structs::YatfHeader& YatfReader::GetHeader() const {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -6,3 +6,13 @@ set_target_properties(europa_pack_extractor PROPERTIES
|
||||||
CXX_STANDARD 20
|
CXX_STANDARD 20
|
||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(texdump texdump.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(texdump PUBLIC libeuropa)
|
||||||
|
|
||||||
|
set_target_properties(texdump PROPERTIES
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
)
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// EuropaTools
|
||||||
|
//
|
||||||
|
// (C) 2021-2022 modeco80 <lily.modeco80@protonmail.ch>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <europa/io/YatfReader.h>
|
||||||
|
|
||||||
|
#include <pixel/ImageWriter.h>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if(argc != 2) {
|
||||||
|
std::cout << "Usage: " << argv[0] << " [path to Europa PAK file]";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream ifs(argv[1], std::ifstream::binary);
|
||||||
|
|
||||||
|
if(!ifs) {
|
||||||
|
std::cout << "Invalid file \"" << argv[1] << "\"\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
europa::io::YatfReader reader(ifs);
|
||||||
|
|
||||||
|
if(reader.Invalid()) {
|
||||||
|
std::cout << "Invalid YATF file \"" << argv[1] << "\"\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.ReadImage();
|
||||||
|
|
||||||
|
|
||||||
|
pixel::ImageWriter writer{};
|
||||||
|
|
||||||
|
auto outPath = fs::path(argv[1]).replace_extension(".png");
|
||||||
|
|
||||||
|
writer.SetImage(reader.GetImage());
|
||||||
|
|
||||||
|
writer.WritePng(outPath);
|
||||||
|
|
||||||
|
std::cout << "Wrote image to " << outPath << '\n';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue