Porytiles
Loading...
Searching...
No Matches
command_decompile_tileset.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <memory>
4#include <string>
5#include <unistd.h>
6
7#include "CLI/CLI.hpp"
8#include "fruit/fruit.h"
9
51
52#include "command.hpp"
53#include "option.hpp"
54
55class DecompileTilesetCommand final : public Command {
56 public:
57 explicit DecompileTilesetCommand(CLI::App &parent_app)
58 : Command{parent_app, kCommandName, kCommandDesc, kCommandGroup}
59 {
60 CLI::App &cmd = get_app();
61 cmd.add_option("<tileset-name>", tileset_name_, "Name of the tileset to decompile")->required();
62 project_root_opt_.RegisterOpt(cmd);
63 porytiles::register_config_options(cmd, cli_storage_);
64 }
65
66 void Run() override
67 {
68 using namespace porytiles;
69
70 // Use Fruit DI to inject TextFormatter based on no_color flag
71 const bool no_color = !isatty(STDERR_FILENO); // Disable color when stderr is not a terminal
72 fruit::Injector injector{di::get_formatter_component, no_color};
73 auto text_formatter = injector.get<TextFormatter *>();
74
75 // Create unfiltered diag for config bootstrapping (so config-loading warnings always show)
76 auto stderr_diag = std::make_unique<StderrStyledUserDiagnostics>(text_formatter);
77
78 std::filesystem::path project_root = project_root_opt_.project_root();
79 std::filesystem::path fieldmap_header_root_relative{"include/fieldmap.h"};
80 std::filesystem::path behaviors_header_root_relative{"include/constants/metatile_behaviors.h"};
81 std::filesystem::path global_fieldmap_header_root_relative{"include/global.fieldmap.h"};
82
83 // Setup layered configuration (CLI options have highest priority)
84 std::vector<std::unique_ptr<ConfigProvider>> providers{};
85 providers.push_back(std::make_unique<CliOptionProvider>(cli_storage_));
86 auto yaml_provider = std::make_unique<YamlFileProvider>(text_formatter, stderr_diag.get(), project_root);
87 auto *yaml_provider_ptr = yaml_provider.get();
88 providers.push_back(std::move(yaml_provider));
89 providers.push_back(
90 std::make_unique<HeaderDefineProvider>(project_root, fieldmap_header_root_relative, text_formatter));
91 providers.push_back(std::make_unique<MetatilesHeaderProvider>(project_root, text_formatter));
92 providers.push_back(std::make_unique<DefaultProvider>());
93 LazyLayeredConfig config{text_formatter, std::move(providers)};
94
95 // Eagerly validate all YAML config files for unknown keys
96 if (yaml_provider_ptr->preload_and_validate(ConfigScopeType::tileset, tileset_name_)) {
97 const auto validation_err = ChainableResult<void>{FormattableError{
98 "Configuration validation failed for tileset '{}'.", FormatParam{tileset_name_, Style::bold}}};
99 stderr_diag->fatal(validation_err);
100 throw CLI::RuntimeError{1};
101 }
102
103 // Eagerly validate metatile-attr-size to fail fast before any file I/O
104 auto attr_size_check = config.metatile_attr_size(ConfigScopeType::tileset, tileset_name_);
105 if (!attr_size_check.has_value()) {
106 stderr_diag->fatal(attr_size_check);
107 throw CLI::RuntimeError{1};
108 }
109 const std::size_t metatile_attr_size = attr_size_check.value().value();
110
111 // Helper to safely extract filter patterns from config, falling back to empty on error
112 auto get_filter_patterns =
113 [&](ChainableResult<ConfigValue<std::vector<std::string>>> result) -> std::vector<std::string> {
114 if (result.has_value()) {
115 return std::move(result).value().value();
116 }
117 stderr_diag->fatal(result);
118 return {};
119 };
120
121 // Build diagnostic filters from config values
122 DiagnosticTagFilter warning_filter{
123 get_filter_patterns(config.diagnostic_warnings_exclude(ConfigScopeType::tileset, tileset_name_)),
124 get_filter_patterns(config.diagnostic_warnings_include(ConfigScopeType::tileset, tileset_name_))};
125
126 DiagnosticTagFilter remark_filter{
127 get_filter_patterns(config.diagnostic_remarks_exclude(ConfigScopeType::tileset, tileset_name_)),
128 get_filter_patterns(config.diagnostic_remarks_include(ConfigScopeType::tileset, tileset_name_))};
129
130 // Wrap with filter decorator for all subsequent operations
131 auto diag = std::make_unique<FilteredUserDiagnostics>(
132 text_formatter, stderr_diag.get(), std::move(warning_filter), std::move(remark_filter));
133
134 std::unique_ptr<TilePrinter> tile_printer = std::make_unique<AsciiTilePrinter>(text_formatter);
135 std::unique_ptr<PalettePrinter> pal_printer = std::make_unique<ColorPalettePrinter>(text_formatter);
136
137 // Initialize stateless services
138 PngRgbaImageLoader png_rgba_loader{};
139 PngIndexedImageLoader png_indexed_loader{};
140 PngRgbaImageSaver png_rgba_saver{};
141 PngIndexedImageSaver png_indexed_saver{};
142 JascPalLoader jasc_loader{text_formatter};
143 JascPalSaver jasc_saver{text_formatter};
144 AnimJsonParser anim_json_parser{text_formatter};
145 AnimCodeParser anim_code_parser{text_formatter, diag.get()};
146 AnimCodeGenerator anim_code_generator{};
147
148 // Setup primary importer and compiler
149 PrimaryTilesetDecompiler decompiler{&config, text_formatter, diag.get(), tile_printer.get(), pal_printer.get()};
150 TilesetCompiler compiler{&config, text_formatter, diag.get(), tile_printer.get(), pal_printer.get()};
151
152 // Setup behavior map provider
153 HeaderBehaviorMapProvider behavior_map_provider{
154 project_root / behaviors_header_root_relative, text_formatter, diag.get()};
155
156 // Setup metadata provider, tileset manager
157 ProjectTilesetMetadataProvider metadata_provider{project_root, text_formatter, diag.get()};
158 ProjectTilesetMetadataWriter metadata_writer{project_root, text_formatter};
159 IncbinDeclarationAppender incbin_appender{project_root, text_formatter};
160 ProjectTilesetAnimsModifier tileset_anims_modifier{project_root, &config, diag.get()};
161 ProjectPorytilesTilesetManager tileset_manager{
162 project_root,
163 &metadata_provider,
164 &metadata_writer,
165 &config,
166 diag.get(),
167 &incbin_appender,
168 &tileset_anims_modifier};
169
170 // Detect base game
171 BaseGameDetector base_game_detector{project_root, text_formatter, diag.get()};
172 auto base_game_result = base_game_detector.detect();
173 if (!base_game_result.has_value()) {
174 diag->fatal(base_game_result);
175 throw CLI::RuntimeError{1};
176 }
177 const BaseGame base_game = base_game_result.value();
178
179 // Conditionally create terrain/encounter providers for FireRed
180 std::unique_ptr<HeaderTerrainTypeMapProvider> terrain_provider;
181 std::unique_ptr<HeaderEncounterTypeMapProvider> encounter_provider;
182 if (base_game == BaseGame::pokefirered) {
183 terrain_provider = std::make_unique<HeaderTerrainTypeMapProvider>(
184 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
185 encounter_provider = std::make_unique<HeaderEncounterTypeMapProvider>(
186 project_root / global_fieldmap_header_root_relative, text_formatter, diag.get());
187 }
188
189 // Setup attributes CSV loader (after base game detection for format validation)
190 AttributesCsvLoader attributes_csv_loader{
191 text_formatter, &behavior_map_provider, base_game, terrain_provider.get(), encounter_provider.get()};
192
193 // Setup the tileset repository
195 project_root, &config, &metadata_provider, text_formatter, diag.get()};
196 ProjectTilesetArtifactReader artifact_reader{
197 project_root,
198 metatile_attr_size,
199 &png_rgba_loader,
200 &png_indexed_loader,
201 &jasc_loader,
202 &attributes_csv_loader,
203 &anim_json_parser,
204 &anim_code_parser,
205 &metadata_provider};
206 ProjectTilesetArtifactWriter artifact_writer{
207 &config,
208 &config,
209 project_root,
210 base_game,
211 metatile_attr_size,
212 text_formatter,
213 diag.get(),
214 &png_rgba_saver,
215 &png_indexed_saver,
216 &jasc_saver,
217 &anim_json_parser,
218 &anim_code_generator,
219 &behavior_map_provider,
220 terrain_provider.get(),
221 encounter_provider.get()};
222 ProjectArtifactChecksumProvider checksum_provider{project_root};
223 TilesetRepo repo{
224 &checksum_provider, &metadata_provider, &key_provider, &artifact_reader, &artifact_writer, diag.get()};
225
226 DecompilePrimaryTileset decompile_use_case{
227 &repo, &decompiler, &compiler, &metadata_provider, &tileset_manager, &config, &config, diag.get()};
228
229 // Run the use case
230 auto decompile_result = decompile_use_case.decompile(tileset_name_);
231 if (!decompile_result.has_value()) {
232 const auto fail_result = ChainableResult<std::unique_ptr<Tileset>>{
233 FormattableError{"Failed to decompile tileset '{}'.", FormatParam{tileset_name_, Style::bold}},
234 decompile_result};
235 diag->fatal(fail_result);
236 throw CLI::RuntimeError{1};
237 }
238 }
239
240 private:
241 static constexpr auto kCommandName = "decompile-tileset";
242 static constexpr auto kCommandDesc =
243 "Decompile a tileset -- update the Porytiles assets to match the Porymap assets.";
244 static constexpr auto kCommandGroup = "COMMANDS";
245 std::string tileset_name_;
246 OptProjectRoot project_root_opt_;
247 porytiles::CliOptionStorage cli_storage_;
248};
Command is an abstract class that provides basic command functionality for the Porytiles CLI driver.
Definition command.hpp:18
CLI::App & get_app() const
Definition command.hpp:44
DecompileTilesetCommand(CLI::App &parent_app)
std::filesystem::path project_root() const
Definition option.hpp:58
void RegisterOpt(CLI::App &app) override
Definition option.hpp:46
Generates C header code for tileset animations.
Parses C code to extract tileset animation parameters using callback chain discovery.
Parses and writes animation configuration JSON files (anim.json).
A service that loads metatile attributes from a CSV file.
Detects the base game of a decompilation project.
ChainableResult< BaseGame > detect() const
Detects the base game for the project.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
Use case for decompiling a primary Tileset.
ChainableResult< void > decompile(const std::string &tileset_name) const
Decompiles the primary Tileset with the given tileset name.
Regex-based include/exclude filter for diagnostic tags.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
Loads behavior mappings from a C/C++ header file containing behavior definitions.
Appends INCBIN declarations for Porytiles-managed tileset assets.
An implementation of FilePalLoader that loads palettes from JASC-PAL (Paintshop Pro) pal files.
An implementation of FilePalSaver that saves palettes to JASC-PAL (Paintshop Pro) pal files.
A Config implementation that lazily pulls a config value by consulting multiple priority-ordered back...
An image loader that reads PNG files to create an Image with an index pixel type.
An image saver that saves PNG files from an Image with an index pixel type.
An image loader that reads PNG files to create an Image with an Rgba32 pixel type.
An image saver that saves PNG files from an Image with an Rgba32 pixel type.
Pokeemerald project filesystem-based implementation of ArtifactChecksumProvider.
Manages Porytiles-owned tilesets via TilesetManifest JSON files.
Modifies tileset_anims.c to include Porytiles-managed animation code headers.
Provides a pokeemerald project filesystem-based implementation for TilesetArtifactKeyProvider.
Provides a filesystem-based implementation for TilesetArtifactReader.
Provides a filesystem-based implementation for TilesetArtifactWriter.
Provides a pokeemerald project filesystem-based implementation for TilesetMetadataProvider.
Provides surgical update capability for tileset metadata in headers.h files.
Abstract base class for applying text styling with context-aware formatting.
Service that compiles a Tileset (primary or secondary).
Repository interface for the Tileset aggregate root.
BaseGame
Identifies the target base game for a decompilation project.
Definition base_game.hpp:20
void register_config_options(CLI::App &app, CliOptionStorage &storage)
Registers all config options with a CLI11 App.
Storage struct for CLI option values.