yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
z3dk_wrapper.cc
Go to the documentation of this file.
1#include "core/z3dk_wrapper.h"
2
3#include <algorithm>
4#include <cstdint>
5#include <filesystem>
6#include <string>
7#include <vector>
8
9#include "absl/status/status.h"
10#include "absl/strings/str_cat.h"
11#include "util/json.h"
12#if __has_include("z3dk_core/assembler.h")
13#include "z3dk_core/assembler.h"
14#include "z3dk_core/emit.h"
15#include "z3dk_core/lint.h"
16#else
17namespace z3dk {
19struct Diagnostic {
21 std::string message;
22 std::string filename;
23 int line = 0;
24 int column = 0;
25 std::string raw;
26};
27struct Label {
28 std::string name;
29 uint32_t address = 0;
30 bool used = false;
31};
32struct Define {
33 std::string name;
34 std::string value;
35};
37 int pc_offset = 0;
38 int snes_offset = 0;
39 int num_bytes = 0;
40};
41struct SourceFile {
42 int id = 0;
43 uint32_t crc = 0;
44 std::string path;
45};
47 uint32_t address = 0;
48 int file_id = 0;
49 int line = 0;
50};
51struct SourceMap {
52 std::vector<SourceFile> files;
53 std::vector<SourceMapEntry> entries;
54};
56 std::string patch_path;
57 std::vector<uint8_t> rom_data;
58 std::vector<std::string> include_paths;
59 std::vector<std::pair<std::string, std::string>> defines;
60 std::string std_includes_path;
61 std::string std_defines_path;
63};
65 bool success = false;
66 std::vector<Diagnostic> diagnostics;
67 std::vector<Label> labels;
68 std::vector<Define> defines;
69 std::vector<WrittenBlock> written_blocks;
70 std::vector<uint8_t> rom_data;
71 int rom_size = 0;
73};
74class Assembler {
75 public:
77};
79 uint32_t start = 0;
80 uint32_t end = 0;
81 std::string reason;
82};
83struct Hook {
84 std::string name;
85 uint32_t address = 0;
86 int size = 0;
87};
89 bool warn_unknown_width = true;
91 bool warn_org_collision = true;
94 bool warn_stack_balance = true;
95 bool warn_hook_return = true;
96 std::vector<MemoryRange> prohibited_memory_ranges;
97};
98struct LintResult {
99 std::vector<Diagnostic> diagnostics;
100 bool success() const { return true; }
101};
102LintResult RunLint(const AssembleResult& result, const LintOptions& options);
103std::string DiagnosticsListToJson(const std::vector<Diagnostic>& diagnostics,
104 bool success);
105std::string HooksToJson(const AssembleResult& result,
106 const std::string& rom_path);
107std::string AnnotationsToJson(const AssembleResult& result);
108std::string SourceMapToJson(const SourceMap& map);
109std::string SymbolsToMlb(const std::vector<Label>& labels);
110} // namespace z3dk
111#endif
112
113namespace yaze {
114namespace core {
115
116namespace {
117
118constexpr std::size_t kMinLoromScratchBytes = 0x80000; // 512 KiB
119
129
133 diag.message = d.message;
134 diag.file = d.filename;
135 diag.line = d.line;
136 diag.column = d.column;
137 diag.raw = d.raw;
138 return diag;
139}
140
142 AsarPatchResult* out) {
143 out->structured_diagnostics.push_back(diag);
145 out->warnings.push_back(
146 diag.file.empty()
147 ? diag.message
148 : absl::StrCat(diag.file, ":", diag.line, ": ", diag.message));
149 } else if (diag.severity == AssemblyDiagnosticSeverity::kError) {
150 out->errors.push_back(
151 diag.file.empty()
152 ? diag.message
153 : absl::StrCat(diag.file, ":", diag.line, ": ", diag.message));
154 }
155}
156
157std::vector<AssemblyDiagnostic> BuildAnnotationNotes(
158 const std::string& annotations_json) {
159 std::vector<AssemblyDiagnostic> notes;
160 if (annotations_json.empty()) {
161 return notes;
162 }
163
164 Json root;
165 try {
166 root = Json::parse(annotations_json);
167 } catch (...) {
168 return notes;
169 }
170
171 if (!root.contains("annotations") || !root["annotations"].is_array()) {
172 return notes;
173 }
174
175 for (const auto& item : root["annotations"]) {
178 const std::string type = item.value("type", "annotation");
179 const std::string label = item.value("label", "");
180 const std::string expr = item.value("expr", "");
181 const std::string note = item.value("note", "");
182 if (!label.empty()) {
183 diag.message = absl::StrCat("@", type, " ", label);
184 } else if (!expr.empty()) {
185 diag.message = absl::StrCat("@", type, " ", expr);
186 } else if (!note.empty()) {
187 diag.message = absl::StrCat("@", type, " ", note);
188 } else {
189 diag.message = absl::StrCat("@", type);
190 }
191
192 const std::string source = item.value("source", "");
193 if (!source.empty()) {
194 const size_t colon = source.find_last_of(':');
195 if (colon != std::string::npos) {
196 diag.file = source.substr(0, colon);
197 try {
198 diag.line = std::stoi(source.substr(colon + 1));
199 } catch (...) {
200 diag.file = source;
201 diag.line = 0;
202 }
203 } else {
204 diag.file = source;
205 }
206 }
207 notes.push_back(std::move(diag));
208 }
209 return notes;
210}
211
213 z3dk::LintOptions lint_options;
214 lint_options.warn_unused_symbols = options.warn_unused_symbols;
216 lint_options.warn_unknown_width = options.warn_unknown_width;
217 lint_options.warn_org_collision = options.warn_org_collision;
218 lint_options.warn_unauthorized_hook = options.warn_unauthorized_hook;
219 lint_options.warn_stack_balance = options.warn_stack_balance;
220 lint_options.warn_hook_return = options.warn_hook_return;
221 for (const auto& range : options.prohibited_memory_ranges) {
222 lint_options.prohibited_memory_ranges.push_back(
223 {.start = range.start, .end = range.end, .reason = range.reason});
224 }
225 return lint_options;
226}
227
229 std::vector<std::string>* errors,
230 std::vector<std::string>* warnings) {
231 errors->clear();
232 warnings->clear();
233 for (const auto& d : result.structured_diagnostics) {
234 if (d.severity == AssemblyDiagnosticSeverity::kNote) {
235 continue;
236 }
237 std::string flat = d.file.empty()
238 ? d.message
239 : absl::StrCat(d.file, ":", d.line, ": ", d.message);
240 if (d.severity == AssemblyDiagnosticSeverity::kWarning) {
241 warnings->push_back(std::move(flat));
242 } else {
243 errors->push_back(std::move(flat));
244 }
245 }
246}
247
248// Convert z3dk's AssembleResult into the editor-shaped AsarPatchResult.
249// We always populate structured_diagnostics; the flat errors/warnings
250// vectors are kept in sync for legacy consumers.
252 AsarPatchResult out;
253 out.success = src.success;
254 out.rom_size = static_cast<uint32_t>(src.rom_size);
255 out.crc32 = 0;
256
257 out.structured_diagnostics.reserve(src.diagnostics.size());
258 for (const auto& d : src.diagnostics) {
260 }
261
262 out.symbols.reserve(src.labels.size());
263 for (const auto& label : src.labels) {
264 AsarSymbol sym;
265 sym.name = label.name;
266 sym.address = label.address;
267 sym.line = 0;
268 out.symbols.push_back(std::move(sym));
269 }
270
271 return out;
272}
273
274} // namespace
275
276// Defined out-of-line because last_result_ holds a unique_ptr to a
277// forward-declared z3dk type; the destructor must see the complete type.
278Z3dkWrapper::Z3dkWrapper() = default;
279Z3dkWrapper::~Z3dkWrapper() = default;
280Z3dkWrapper::Z3dkWrapper(Z3dkWrapper&&) noexcept = default;
281Z3dkWrapper& Z3dkWrapper::operator=(Z3dkWrapper&&) noexcept = default;
282
283absl::Status Z3dkWrapper::Initialize() {
284 initialized_ = true;
285 return absl::OkStatus();
286}
287
288std::string Z3dkWrapper::GetVersion() const {
289 // z3dk-core pins a specific Asar fork; version string is informational.
290 return "z3dk-core (embedded Asar fork)";
291}
292
293absl::StatusOr<AsarPatchResult> Z3dkWrapper::ApplyPatch(
294 const std::string& patch_path, std::vector<uint8_t>& rom_data,
295 const std::vector<std::string>& include_paths) {
296 Z3dkAssembleOptions options;
297 options.include_paths = include_paths;
298 return ApplyPatch(patch_path, rom_data, options);
299}
300
301absl::StatusOr<AsarPatchResult> Z3dkWrapper::ApplyPatch(
302 const std::string& patch_path, std::vector<uint8_t>& rom_data,
303 const Z3dkAssembleOptions& options) {
304 return Assemble(patch_path, rom_data, options, /*update_apply_cache=*/true);
305}
306
307absl::StatusOr<AsarPatchResult> Z3dkWrapper::Assemble(
308 const std::string& patch_path, std::vector<uint8_t>& rom_data,
309 const Z3dkAssembleOptions& options, bool update_apply_cache) {
310 if (!initialized_) {
311 auto st = Initialize();
312 if (!st.ok()) {
313 return st;
314 }
315 }
316
318 opts.patch_path = patch_path;
319 opts.include_paths = options.include_paths;
320 opts.defines = options.defines;
321 if (!options.mapper.empty()) {
322 const auto mapper_it = std::find_if(
323 opts.defines.begin(), opts.defines.end(),
324 [](const auto& define) { return define.first == "z3dk_mapper"; });
325 if (mapper_it == opts.defines.end()) {
326 opts.defines.emplace_back("z3dk_mapper", options.mapper);
327 }
328 }
330 opts.std_defines_path = options.std_defines_path;
332 opts.rom_data = rom_data; // Copy in; z3dk resizes/edits its own buffer.
333 if (options.rom_size > 0 &&
334 opts.rom_data.size() < static_cast<std::size_t>(options.rom_size)) {
335 opts.rom_data.resize(static_cast<std::size_t>(options.rom_size), 0);
336 }
337 if (opts.rom_data.size() < kMinLoromScratchBytes) {
338 opts.rom_data.resize(kMinLoromScratchBytes, 0);
339 }
340
341 z3dk::Assembler assembler;
342 auto z_result = assembler.Assemble(opts);
343 auto result = ConvertResult(z_result);
344
345 if (z_result.success) {
346 z3dk::LintResult lint_result =
347 z3dk::RunLint(z_result, BuildLintOptions(options));
348 for (const auto& diag : lint_result.diagnostics) {
349 AppendStructuredDiagnostic(ConvertDiagnostic(diag), &result);
350 }
351
352 result.symbols_mlb = z3dk::SymbolsToMlb(z_result.labels);
353 result.sourcemap_json = z3dk::SourceMapToJson(z_result.source_map);
354 result.annotations_json = z3dk::AnnotationsToJson(z_result);
355 result.hooks_json = z3dk::HooksToJson(z_result, options.hooks_rom_path);
356 result.lint_json = z3dk::DiagnosticsListToJson(lint_result.diagnostics,
357 lint_result.success());
358
359 const auto annotation_notes = BuildAnnotationNotes(result.annotations_json);
360 for (const auto& diag : annotation_notes) {
361 result.structured_diagnostics.push_back(diag);
362 }
363
364 if (!lint_result.success()) {
365 result.success = false;
366 }
367 }
368
369 RebuildLegacyDiagnosticCaches(result, &last_errors_, &last_warnings_);
370
371 if (!update_apply_cache) {
372 return result;
373 }
374
375 if (result.success) {
376 // Cache the full assemble result so RunLintOnLastResult can re-run lint
377 // without re-assembling. rom_data for the caller is taken as a copy from
378 // the cache — the ~4 MiB memcpy is negligible next to assemble time.
379 last_result_ = std::make_unique<z3dk::AssembleResult>(std::move(z_result));
380 rom_data = last_result_->rom_data;
381 result.rom_size = static_cast<uint32_t>(rom_data.size());
382 // Rebuild symbol table from labels.
383 symbol_table_.clear();
384 for (const auto& s : result.symbols) {
385 symbol_table_[s.name] = s;
386 }
387 } else {
389 }
390
391 return result;
392}
393
395 const std::string& asm_path,
396 const std::vector<std::string>& include_paths) {
397 Z3dkAssembleOptions options;
398 options.include_paths = include_paths;
399 return ValidateAssembly(asm_path, options);
400}
401
402absl::Status Z3dkWrapper::ValidateAssembly(const std::string& asm_path,
403 const Z3dkAssembleOptions& options) {
404 if (!std::filesystem::exists(asm_path)) {
405 return absl::NotFoundError(
406 absl::StrCat("Assembly file not found: ", asm_path));
407 }
408 std::vector<uint8_t> scratch;
409 auto result_or =
410 Assemble(asm_path, scratch, options, /*update_apply_cache=*/false);
411 if (!result_or.ok()) {
412 return result_or.status();
413 }
414 if (!result_or->success) {
415 return absl::InternalError("Assembly validation failed");
416 }
417 return absl::OkStatus();
418}
419
420std::optional<AsarSymbol> Z3dkWrapper::FindSymbol(
421 const std::string& name) const {
422 auto it = symbol_table_.find(name);
423 if (it == symbol_table_.end())
424 return std::nullopt;
425 return it->second;
426}
427
428absl::StatusOr<std::vector<AssemblyDiagnostic>>
430 if (!last_result_) {
431 return absl::FailedPreconditionError(
432 "No assemble result cached; call ApplyPatch first.");
433 }
434 z3dk::LintResult lint =
435 z3dk::RunLint(*last_result_, BuildLintOptions(options));
436 std::vector<AssemblyDiagnostic> out;
437 out.reserve(lint.diagnostics.size());
438 for (const auto& d : lint.diagnostics) {
439 out.push_back(ConvertDiagnostic(d));
440 }
441 return out;
442}
443
445 symbol_table_.clear();
446 last_result_.reset();
447}
448
451 last_errors_.clear();
452 last_warnings_.clear();
453}
454
455} // namespace core
456} // namespace yaze
static Json parse(const std::string &)
Definition json.h:36
bool is_array() const
Definition json.h:58
bool contains(const std::string &) const
Definition json.h:53
absl::StatusOr< AsarPatchResult > Assemble(const std::string &patch_path, std::vector< uint8_t > &rom_data, const Z3dkAssembleOptions &options, bool update_apply_cache)
std::vector< std::string > last_errors_
absl::StatusOr< std::vector< AssemblyDiagnostic > > RunLintOnLastResult(const Z3dkAssembleOptions &options) const
std::optional< AsarSymbol > FindSymbol(const std::string &name) const
absl::Status Initialize()
std::vector< std::string > last_warnings_
std::string GetVersion() const
absl::Status ValidateAssembly(const std::string &asm_path, const std::vector< std::string > &include_paths={})
absl::StatusOr< AsarPatchResult > ApplyPatch(const std::string &patch_path, std::vector< uint8_t > &rom_data, const std::vector< std::string > &include_paths={})
std::unique_ptr<::z3dk::AssembleResult > last_result_
std::map< std::string, AsarSymbol > symbol_table_
AssembleResult Assemble(const AssembleOptions &options) const
void RebuildLegacyDiagnosticCaches(const AsarPatchResult &result, std::vector< std::string > *errors, std::vector< std::string > *warnings)
std::vector< AssemblyDiagnostic > BuildAnnotationNotes(const std::string &annotations_json)
z3dk::LintOptions BuildLintOptions(const Z3dkAssembleOptions &options)
AssemblyDiagnosticSeverity ConvertSeverity(z3dk::DiagnosticSeverity s)
AssemblyDiagnostic ConvertDiagnostic(const z3dk::Diagnostic &d)
void AppendStructuredDiagnostic(const AssemblyDiagnostic &diag, AsarPatchResult *out)
AsarPatchResult ConvertResult(const z3dk::AssembleResult &src)
LintResult RunLint(const AssembleResult &result, const LintOptions &options)
std::string SourceMapToJson(const SourceMap &map)
std::string AnnotationsToJson(const AssembleResult &result)
std::string HooksToJson(const AssembleResult &result, const std::string &rom_path)
std::string SymbolsToMlb(const std::vector< Label > &labels)
std::string DiagnosticsListToJson(const std::vector< Diagnostic > &diagnostics, bool success)
DiagnosticSeverity
Asar patch result information.
std::vector< AssemblyDiagnostic > structured_diagnostics
std::vector< std::string > errors
std::vector< AsarSymbol > symbols
std::vector< std::string > warnings
Symbol information extracted from Asar assembly.
AssemblyDiagnosticSeverity severity
std::vector< Z3dkMemoryRange > prohibited_memory_ranges
std::vector< std::string > include_paths
std::vector< std::pair< std::string, std::string > > defines
std::vector< uint8_t > rom_data
std::string std_includes_path
std::string std_defines_path
std::vector< std::pair< std::string, std::string > > defines
std::vector< std::string > include_paths
std::vector< Label > labels
std::vector< Diagnostic > diagnostics
std::vector< Define > defines
std::vector< WrittenBlock > written_blocks
std::vector< uint8_t > rom_data
std::string value
std::string name
DiagnosticSeverity severity
std::string message
std::string raw
std::string filename
std::string name
uint32_t address
uint32_t address
std::string name
std::vector< MemoryRange > prohibited_memory_ranges
std::vector< Diagnostic > diagnostics
bool success() const
std::string reason
std::string path
std::vector< SourceFile > files
std::vector< SourceMapEntry > entries