Porytiles
Loading...
Searching...
No Matches
project_tileset_artifact_reader.cpp
Go to the documentation of this file.
2
3#include <expected>
4#include <fstream>
5#include <functional>
6#include <iterator>
7
8#include "fmt/format.h"
9
17
18namespace {
19
20using namespace porytiles2;
21
22ChainableResult<void> import_layer_png(
23 Tileset &dest,
24 const ArtifactKey &src_key,
25 const PngRgbaImageLoader &loader,
26 const std::function<void(PorytilesTilesetComponent &, const Image<Rgba32> &)> &layer_img_setter)
27{
28 auto image_result = loader.load_from_file(src_key.key());
29 if (!image_result.has_value()) {
30 switch (image_result.error().type()) {
31 // TODO: this shouldn't load a blank image, it should just error. To support the "import" case, we're going
32 // to create a special tileset operation called "import" which is distinct from "load", and which assumes a
33 // Porytiles component is not present.
34 case ImageLoadError::Type::file_not_found:
35 layer_img_setter(dest.porytiles_component(), Image<Rgba32>{});
36 return {};
37 case ImageLoadError::Type::unsupported_channel_count:
38 case ImageLoadError::Type::other_load_error: {
39 const auto error_msg = fmt::format("failed to load layer image: {}", src_key.key());
40 return ChainableResult<void>{FormattableError{error_msg}, image_result};
41 }
42 default:
43 panic("unhandled ImageLoadError type");
44 }
45 }
46 layer_img_setter(dest.porytiles_component(), *image_result.value());
47 return {};
48}
49
50ChainableResult<void> import_metatiles_bin(Tileset &dest, const ArtifactKey &src_key)
51{
52 std::ifstream metatiles_bin{src_key.key(), std::ios::binary};
53 const std::vector<unsigned char> data_buf{std::istreambuf_iterator(metatiles_bin), {}};
54
55 if (data_buf.size() % 2 != 0) {
56 return FormattableError{"metatiles.bin size is not a multiple of 2 bytes, probably corrupted"};
57 }
58
59 for (unsigned int byte_index = 0; byte_index < data_buf.size(); byte_index += 2) {
60 TilemapEntry entry{};
61 const std::uint16_t lower_byte = data_buf.at(byte_index);
62 const std::uint16_t upper_byte = data_buf.at(byte_index + 1);
63 const std::uint16_t entry_bits = (upper_byte << 8) | lower_byte;
64
65 // -------- Metatile BIN Structure --------
66 // The metatiles.bin file contains a sequence of tilemap entries, which are each two bytes with the following
67 // structure:
68 //
69 // 0000 00XX XXXX XXXX
70 // least significant 10 bits are the tile index
71 //
72 // 0000 0X00 0000 0000
73 // 11th bit is the hflip bit
74 //
75 // 0000 X000 0000 0000
76 // 12th bit is the vflip bit
77 //
78 // XXXX 0000 0000 0000
79 // top 4 bits are pal index
80
81 entry.tile_index(entry_bits & 0x03FF);
82 entry.hflip((entry_bits >> 10) & 0x0001);
83 entry.vflip((entry_bits >> 11) & 0x0001);
84 entry.pal_index((entry_bits >> 12) & 0x000F);
85
87 }
88
89 return {};
90}
91
92ChainableResult<void> import_emerald_metatile_attributes(Tileset &dest, const ArtifactKey &src_key)
93{
94 std::ifstream metatile_attr_bin{src_key.key(), std::ios::binary};
95 const std::vector<unsigned char> data_buf{std::istreambuf_iterator(metatile_attr_bin), {}};
96
97 if (data_buf.size() % attr::bytes_per_attr_emerald != 0) {
98 return FormattableError{fmt::format(
99 "metatile_attributes.bin size is not a multiple of {} bytes, probably corrupted",
101 }
102
103 std::size_t metatile_count = data_buf.size() / attr::bytes_per_attr_emerald;
104 for (std::size_t metatile_index = 0; metatile_index < metatile_count; metatile_index++) {
105 std::uint16_t byte0 = data_buf.at((metatile_index * attr::bytes_per_attr_emerald));
106 std::uint16_t byte1 = data_buf.at((metatile_index * attr::bytes_per_attr_emerald) + 1);
107 std::uint16_t attribute = (byte1 << 8) | byte0;
108
109 auto layer_type_result = attr::layer_type_from_int(attribute >> 12 & 0x000F);
110 if (!layer_type_result.has_value()) {
112 FormattableError{"invalid layer type for metatile '{}'", FormatParam{metatile_index, Style::bold}},
113 layer_type_result};
114 }
115 MetatileAttribute metatile_attribute{layer_type_result.value(), static_cast<std::uint16_t>(attribute & 0x00FF)};
116 dest.porymap_component().push_back_attribute(metatile_attribute);
117 }
118
119 return {};
120}
121
122ChainableResult<void> import_firered_metatile_attributes(Tileset &dest, const ArtifactKey &src_key)
123{
124 std::ifstream metatile_attr_bin{src_key.key(), std::ios::binary};
125 const std::vector<unsigned char> data_buf{std::istreambuf_iterator(metatile_attr_bin), {}};
126
127 if (data_buf.size() % attr::bytes_per_attr_firered != 0) {
128 return FormattableError{fmt::format(
129 "metatile_attributes.bin size is not a multiple of {} bytes, probably corrupted",
131 }
132
133 std::size_t metatile_count = data_buf.size() / attr::bytes_per_attr_emerald;
134 for (std::size_t metatile_index = 0; metatile_index < metatile_count; metatile_index++) {
135 std::uint32_t byte0 = data_buf.at((metatile_count * attr::bytes_per_attr_firered));
136 std::uint32_t byte1 = data_buf.at((metatile_count * attr::bytes_per_attr_firered) + 1);
137 std::uint32_t byte2 = data_buf.at((metatile_count * attr::bytes_per_attr_firered) + 2);
138 std::uint32_t byte3 = data_buf.at((metatile_count * attr::bytes_per_attr_firered) + 3);
139 std::uint32_t attribute = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
140 // attributes.metatileBehavior = attribute & 0x000001FF;
141 // attributes.terrainType = terrainTypeFromInt((attribute >> 9) & 0x0000001F);
142 // attributes.encounterType = encounterTypeFromInt((attribute >> 24) & 0x00000007);
143 // attributes.layerType = layerTypeFromInt((attribute >> 29) & 0x00000003);
144 // TODO: init an attr here and insert into 'dest'
145 }
146
147 return {};
148}
149
150ChainableResult<void> import_tiles_png(Tileset &dest, const ArtifactKey &src_key, const PngIndexedImageLoader &loader)
151{
152 auto image_result = loader.load_from_file(src_key.key());
153 if (!image_result.has_value()) {
154 return FormattableError{fmt::format("failed to load tiles.png: {}", image_result.error())};
155 }
156 dest.porymap_component().tiles_png(*image_result.value());
157 return {};
158}
159
160ChainableResult<void> import_palette(Tileset &dest, const ArtifactKey &src_key, int index, const FilePalLoader &loader)
161{
162 // TODO: don't hardcode 16 here
163 if (index < 0 || index >= 16) {
164 panic(fmt::format("invalid pal index {}: out of range", index));
165 }
166
167 const auto pal_result = loader.load(src_key.key());
168 if (!pal_result.has_value()) {
169 return FormattableError{fmt::format("failed to load: {}", pal_result.error())};
170 }
171 dest.porymap_component().set_pal(pal_result.value(), index);
172
173 return {};
174}
175
176} // namespace
177
178namespace porytiles2 {
179
181ProjectTilesetArtifactReader::read(Tileset &dest, const ArtifactKey &src_key, const TilesetArtifact &artifact) const
182{
183 switch (artifact.type()) {
184 // Porytiles artifacts
186 const auto result = import_layer_png(
187 dest, src_key, *png_rgba_loader_, [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) {
188 comp.bottom(img);
189 });
190 if (!result.has_value()) {
191 return ChainableResult<void>{FormattableError{fmt::format("failed to read bottom.png")}, result};
192 }
193 return {};
194 }
196 const auto result = import_layer_png(
197 dest, src_key, *png_rgba_loader_, [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) {
198 comp.middle(img);
199 });
200 if (!result.has_value()) {
201 return ChainableResult<void>{FormattableError{fmt::format("failed to read middle.png")}, result};
202 }
203 return {};
204 }
206 const auto result = import_layer_png(
207 dest, src_key, *png_rgba_loader_, [](PorytilesTilesetComponent &comp, const Image<Rgba32> &img) {
208 comp.top(img);
209 });
210 if (!result.has_value()) {
211 return ChainableResult<void>{FormattableError{fmt::format("failed to read top.png")}, result};
212 }
213 return {};
214 }
216 panic("TODO: implement");
218 panic("TODO: implement");
220 panic("TODO: implement");
221
222 // Porymap artifacts
224 const auto result = import_metatiles_bin(dest, src_key);
225 if (!result.has_value()) {
226 return ChainableResult<void>{FormattableError{fmt::format("could not import metatiles.bin")}, result};
227 }
228 return {};
229 }
231 // TODO: branch here based on target base game?
232 const auto result = import_emerald_metatile_attributes(dest, src_key);
233 if (!result.has_value()) {
235 FormattableError{fmt::format("could not import metatile_attributes.bin")}, result};
236 }
237 return {};
238 }
240 // TODO: make this a ChainableResult
241 const auto result = import_tiles_png(dest, src_key, *png_indexed_loader_);
242 if (!result.has_value()) {
243 return ChainableResult<void>{FormattableError{fmt::format("could not import tiles.png")}, result};
244 }
245 return {};
246 }
248 panic("TODO: implement");
250 if (!artifact.index().has_value()) {
251 panic("took TilesetArtifact::Type::pal_n branch but missing pal index");
252 }
253 const auto result = import_palette(dest, src_key, artifact.index().value(), *pal_loader_);
254 if (!result.has_value()) {
256 FormattableError{fmt::format("could not import pal {}", artifact.index().value())}, result};
257 }
258 return {};
259 }
260
261 // Default case
262 default:
263 panic("unhandled TilesetArtifact::Type");
264 }
265}
266
267} // namespace porytiles2
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 loads a Palette from a given file.
virtual Result< Palette< Rgba32 > > load(const std::filesystem::path &path) const =0
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:117
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:24
An image loader that reads PNG files to create an Image with an index pixel type.
Result< std::unique_ptr< Image< IndexPixel > > > load_from_file(const std::filesystem::path &path) const
An image loader that reads PNG files to create an Image with an Rgba32 pixel type.
ChainableResult< std::unique_ptr< Image< Rgba32 > >, ImageLoadError > load_from_file(const std::filesystem::path &path) const
void push_back_tilemap_entry(TilemapEntry entry)
Add a TilemapEntry to the end of the entry vector.
void set_pal(Palette< Rgba32 > pal, unsigned int pal_index)
void push_back_attribute(MetatileAttribute attribute)
Add a MetatileAttribute to the end of the attribute vector.
const Image< IndexPixel > & tiles_png() const
ChainableResult< void > read(Tileset &dest, const ArtifactKey &src_key, const TilesetArtifact &artifact) const override
Reads an artifact from the backing store and updates the target Tileset.
Represents a tilemap entry referencing a tile with palette and flip attributes.
unsigned int tile_index() const
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.
Definition tileset.hpp:14
const PorytilesTilesetComponent & porytiles_component() const
Definition tileset.hpp:37
const PorymapTilesetComponent & porymap_component() const
Definition tileset.hpp:47
constexpr std::size_t bytes_per_attr_emerald
ChainableResult< LayerType > layer_type_from_int(std::uint8_t i)
constexpr std::size_t bytes_per_attr_firered
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.hpp:53