12#include "fmt/format.h"
24std::filesystem::path create_tmpdir()
27 auto tmpDir = std::filesystem::temp_directory_path();
29 std::random_device randomDevice;
30 std::mt19937 mersennePrng(randomDevice());
31 std::uniform_int_distribution<uint64_t> uniformIntDistribution(0);
32 std::filesystem::path path;
34 std::stringstream stringStream;
35 stringStream << std::hex << uniformIntDistribution(mersennePrng);
36 path = tmpDir / (
"porytiles_" + stringStream.str());
37 if (std::filesystem::create_directory(path)) {
41 panic(
"tmpfiles::createTmpdir getTmpdirPath took too many tries");
52 if (!result.has_value()) {
61 const std::filesystem::path &path,
64 auto result = saver.
save_to_file(tiles_png, path, tiles_pal_mode);
65 if (!result.has_value()) {
71ChainableResult<void> save_metatiles_bin(
const std::vector<TilemapEntry> &entries,
const std::filesystem::path &path)
73 std::ofstream out{path};
74 for (
const auto &entry : entries) {
76 const auto tile_value =
static_cast<uint16_t
>(
77 (entry.tile_index() & 0x3ff) | ((entry.hflip() & 1) << 10) | ((entry.vflip() & 1) << 11) |
78 ((entry.pal_index() & 0xf) << 12));
79 out << static_cast<std::uint8_t>(tile_value);
80 out << static_cast<std::uint8_t>(tile_value >> 8);
87save_metatile_attributes_bin(
const std::vector<MetatileAttribute> &attributes,
const std::filesystem::path &path)
90 std::ofstream out{path};
91 for (
const auto &attribute : attributes) {
92 const std::uint16_t behavior = attribute.behavior();
93 const auto layer_type =
static_cast<std::uint8_t
>(attribute.layer_type());
95 const auto attribute_value =
static_cast<std::uint16_t
>((behavior & 0xff) | ((layer_type & 0xf) << 12));
96 out << static_cast<std::uint8_t>(attribute_value);
97 out << static_cast<std::uint8_t>(attribute_value >> 8);
106 const auto save_result = saver.
save(pal, path);
107 if (!save_result.has_value()) {
119 if (!transaction_root_.empty()) {
120 return std::unexpected{
"transaction already in progress"};
122 transaction_root_ = create_tmpdir();
129 if (transaction_root_.empty()) {
133 std::vector<std::pair<std::filesystem::path, std::filesystem::path>> backed_up_files;
134 std::vector<std::filesystem::path> new_files;
136 const auto backup_root = create_tmpdir();
139 std::vector<std::pair<std::filesystem::path, std::filesystem::path>> files_to_copy;
140 for (
const auto &entry : std::filesystem::recursive_directory_iterator(transaction_root_)) {
141 if (entry.is_regular_file()) {
142 auto relative_path = std::filesystem::relative(entry.path(), transaction_root_);
143 auto dest_path = project_root_ / relative_path;
144 files_to_copy.emplace_back(entry.path(), dest_path);
149 for (
const auto &dest : files_to_copy | std::views::values) {
150 if (std::filesystem::exists(dest)) {
151 auto backup_relative = std::filesystem::relative(dest, project_root_);
152 auto backup_path = backup_root / backup_relative;
155 std::filesystem::create_directories(backup_path.parent_path());
158 std::filesystem::copy_file(dest, backup_path);
159 backed_up_files.emplace_back(dest, backup_path);
164 for (
const auto &[src, dest] : files_to_copy) {
166 std::filesystem::create_directories(dest.parent_path());
169 const bool is_new_file = !std::filesystem::exists(dest);
172 std::filesystem::copy_file(src, dest, std::filesystem::copy_options::overwrite_existing);
175 new_files.push_back(dest);
180 std::filesystem::remove_all(transaction_root_);
181 std::filesystem::remove_all(backup_root);
182 transaction_root_.clear();
186 catch (
const std::filesystem::filesystem_error &e) {
190 for (
const auto &[original_path, backup_path] : backed_up_files) {
191 if (std::filesystem::exists(backup_path)) {
192 std::filesystem::copy_file(
193 backup_path, original_path, std::filesystem::copy_options::overwrite_existing);
198 for (
const auto &new_file : new_files) {
199 if (std::filesystem::exists(new_file)) {
200 std::filesystem::remove(new_file);
204 catch (
const std::filesystem::filesystem_error &restore_error) {
210 if (std::filesystem::exists(backup_root)) {
211 std::filesystem::remove_all(backup_root);
213 if (std::filesystem::exists(transaction_root_)) {
214 std::filesystem::remove_all(transaction_root_);
216 transaction_root_.clear();
224 if (transaction_root_.empty()) {
225 return std::unexpected{
"no transaction in progress"};
229 if (std::filesystem::exists(transaction_root_)) {
230 std::filesystem::remove_all(transaction_root_);
232 transaction_root_.clear();
235 catch (
const std::filesystem::filesystem_error &e) {
236 transaction_root_.clear();
237 return std::unexpected{fmt::format(
"failed to rollback transaction: {}", e.what())};
244 if (transaction_root_.empty()) {
249 const auto relative_path = std::filesystem::path{dest_key.
key()}.lexically_relative(project_root_);
250 const auto transaction_dest_path = transaction_root_ / relative_path;
253 std::filesystem::create_directories(transaction_dest_path.parent_path());
256 switch (artifact.
type()) {
281 tiles_pal_mode_config, config_->
tiles_pal_mode(src.
name()),
"failed to get tiles_pal_mode config",
void);
282 return save_tiles_png(
285 transaction_dest_path,
286 tiles_pal_mode_config.value());
292 if (!artifact.
index().has_value()) {
293 panic(
"took TilesetArtifact::Type::pal_n branch but missing pal index");
295 const auto index = artifact.
index().value();
301 panic(
"unhandled TilesetArtifact::Type");
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, msg, return_type)
Unwraps a ChainableResult, chaining a new error message on failure.
A type-safe wrapper for artifact keys.
const std::string & key() const
Gets the underlying string value.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A service interface that saves a Palette to a given file.
virtual ChainableResult< void > save(const Palette< Rgba32 > &pal, const std::filesystem::path &path) const =0
A template for two-dimensional images with arbitrarily typed pixel values.
ChainableResult< ConfigValue< TilesPalMode > > tiles_pal_mode(const std::string &tileset) const
A palette container for colors that support transparency checking.
An image saver that saves PNG files from an Image with an index pixel type.
virtual ChainableResult< void > save_to_file(const Image< IndexPixel > &image, const std::filesystem::path &path, TilesPalMode mode) const
An image saver that saves PNG files from an Image with an Rgba32 pixel type.
virtual ChainableResult< void > save_to_file(const Image< Rgba32 > &image, const std::filesystem::path &path) const
const std::vector< TilemapEntry > & metatiles_bin() const
const Image< IndexPixel > & tiles_png() const
const Palette< Rgba32 > & pal_at(unsigned int pal_index) const
const std::vector< MetatileAttribute > & metatile_attributes_bin() const
const Image< Rgba32 > & middle() const
const Image< Rgba32 > & top() const
const Image< Rgba32 > & bottom() const
Result< void > rollback() override
Rolls back all buffered write operations in the current transaction.
ChainableResult< void > write(const ArtifactKey &dest_key, const TilesetArtifact &artifact, const Tileset &src) override
Writes an artifact from a Tileset to the backing store.
ChainableResult< void > commit() override
Commits all buffered write operations in the current transaction.
Result< void > begin_transaction() override
Begins a new transaction for atomic write operations.
Represents a Pokémon Generation III decomp tileset artifact with type and optional metadata.
Type type() const
Gets the artifact type.
@ porytiles_anim_frame
Animation frame PNG for Porytiles-format animation.
@ attributes_csv
CSV file containing metatile attribute overrides.
@ middle_png
Middle layer PNG input image.
@ metatile_attributes_bin
Metatile attributes output for Porymap.
@ bottom_png
Bottom layer PNG input image.
@ top_png
Top layer PNG input image.
@ pal_n
JASC palette data file.
@ metatiles_bin
Metatile data output for Porymap.
@ pal_override_n
JASC palette override file.
@ tiles_png
Combined tile sheet PNG output for Porymap.
@ porymap_anim_frame
Animation frame PNG for Porymap-format animation.
std::optional< unsigned int > index() const
Gets the artifact index if present.
A complete tileset containing both Porytiles and Porymap components.
const PorytilesTilesetComponent & porytiles_component() const
const PorymapTilesetComponent & porymap_component() const
const std::string & name() const
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
std::expected< T, E > Result
A result with some type T on success, otherwise an error of type E.