8#include "absl/strings/ascii.h"
9#include "absl/strings/match.h"
10#include "absl/strings/numbers.h"
11#include "absl/strings/str_cat.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_split.h"
33 const std::vector<uint16_t>& table_words) {
34 std::array<gfx::SnesColor, 32> out{};
35 for (
int i = 0; i < 32; ++i) {
36 if (i % 4 == 0 || i - 1 >=
static_cast<int>(table_words.size())) {
46 return static_cast<uint16_t
>(p[0] | (p[1] << 8));
58 s = absl::StripAsciiWhitespace(s);
62 if (s.front() ==
'$' || absl::StartsWithIgnoreCase(s,
"0x")) {
65 }
else if (!absl::SimpleAtoi(s, &value)) {
68 if (value > 0xFFFFFFFFull)
70 return static_cast<uint32_t
>(value);
75 op = absl::StripAsciiWhitespace(op);
77 return absl::InvalidArgumentError(
"empty dw operand (stray comma?)");
79 constexpr absl::string_view kMacro =
"hexto555";
80 if (absl::StartsWithIgnoreCase(op, kMacro)) {
81 absl::string_view rest =
82 absl::StripAsciiWhitespace(op.substr(kMacro.size()));
83 if (rest.size() < 2 || rest.front() !=
'(' || rest.back() !=
')') {
84 return absl::InvalidArgumentError(absl::StrFormat(
85 "expected hexto555($RRGGBB), got '%s'", std::string(op)));
87 absl::string_view inner = rest.substr(1, rest.size() - 2);
89 if (!rgb.has_value() || *rgb > 0xFFFFFF) {
90 return absl::InvalidArgumentError(
91 absl::StrFormat(
"hexto555 argument '%s' is not a $RRGGBB value",
92 std::string(absl::StripAsciiWhitespace(inner))));
97 if (!word.has_value()) {
98 return absl::InvalidArgumentError(absl::StrFormat(
99 "unsupported dw operand '%s' (expected hexto555($RRGGBB) or a "
103 if (*word > 0xFFFF) {
104 return absl::InvalidArgumentError(absl::StrFormat(
105 "dw operand '%s' does not fit in 16 bits", std::string(op)));
107 return static_cast<uint16_t
>(*word);
111 size_t end = line.find_first_of(
" \t");
112 absl::string_view token = line.substr(0, end);
113 if (!absl::EqualsIgnoreCase(token,
"dw"))
116 end == absl::string_view::npos ? absl::string_view() : line.substr(end);
123 const uint32_t r = (rgb >> 16) & 0xFF;
124 const uint32_t g = (rgb >> 8) & 0xFF;
125 const uint32_t b = rgb & 0xFF;
126 return static_cast<uint16_t
>(((b / 8) << 10) | ((g / 8) << 5) | (r / 8));
130 std::string_view text_in, std::string_view label_in) {
131 const absl::string_view text(text_in.data(), text_in.size());
132 const absl::string_view label(label_in.data(), label_in.size());
133 std::vector<uint16_t> words;
134 bool in_table =
false;
136 int last_table_line = 0;
140 for (absl::string_view raw : absl::StrSplit(text,
'\n')) {
143 absl::string_view line =
144 absl::StripAsciiWhitespace(raw.substr(0, raw.find(
';')));
149 size_t end = line.find_first_of(
": \t");
150 if (line.substr(0, end) != label)
153 label_line = line_no;
154 absl::string_view rest = end == absl::string_view::npos
155 ? absl::string_view()
157 if (!rest.empty() && rest.front() ==
':')
158 rest.remove_prefix(1);
159 line = absl::StripAsciiWhitespace(rest);
164 absl::string_view operands;
165 if (!IsDwDirective(line, &operands)) {
168 last_table_line = line_no;
169 for (absl::string_view op : absl::StrSplit(operands,
',')) {
170 absl::StatusOr<uint16_t> word = ParseDwOperand(op);
172 return absl::InvalidArgumentError(
173 absl::StrFormat(
"line %d: %s", line_no, word.status().message()));
175 words.push_back(*word);
176 if (words.size() >= kMenuPaletteMaxEntries) {
186 return absl::NotFoundError(
187 absl::StrFormat(
"label '%s' not found", std::string(label)));
189 if (words.size() < kMenuPaletteMinEntries) {
190 return absl::InvalidArgumentError(absl::StrFormat(
191 "line %d: '%s' has only %zu dw entries; at least %zu are needed "
192 "(8 sub-palettes x 3 colors + the unused final slot)",
193 last_table_line > 0 ? last_table_line : label_line, std::string(label),
194 words.size(), kMenuPaletteMinEntries));
200 const std::string& path,
const std::string& label) {
201 std::ifstream in(path, std::ios::binary);
203 return absl::NotFoundError(
204 absl::StrFormat(
"could not open '%s' for reading", path));
206 std::ostringstream buffer;
207 buffer << in.rdbuf();
208 const std::string text = buffer.str();
210 absl::StatusOr<std::vector<uint16_t>> words =
212 constexpr absl::string_view kSymbolPrefix =
"Oracle_";
213 if (!words.ok() && absl::IsNotFound(words.status()) &&
214 absl::StartsWith(label, kSymbolPrefix)) {
218 return absl::Status(words.status().code(),
219 absl::StrCat(path,
": ", words.status().message()));
221 return BuildSubpaletteArray(*words);
225 const std::string& code_folder) {
226 namespace fs = std::filesystem;
227 std::vector<fs::path> candidates;
228 if (!project_dir.empty()) {
229 candidates.push_back(fs::path(project_dir) /
"Menu" /
"menu_palette.asm");
231 if (!code_folder.empty()) {
232 fs::path code(code_folder);
233 if (code.is_absolute()) {
234 candidates.push_back(code /
"Menu" /
"menu_palette.asm");
235 }
else if (!project_dir.empty()) {
236 candidates.push_back(fs::path(project_dir) / code /
"Menu" /
240 for (
const fs::path& candidate : candidates) {
242 if (fs::is_regular_file(candidate, ec) && !ec) {
243 return candidate.string();
251 const std::string& label) {
252 std::optional<emu::debug::Symbol> symbol = symbols.
FindSymbol(label);
253 if (!symbol.has_value()) {
254 return absl::NotFoundError(absl::StrFormat(
255 "symbol '%s' not found in the loaded symbol table", label));
257 uint32_t pc =
SnesToPc(symbol->address);
260 if (raw.size() < 64) {
261 return absl::OutOfRangeError(absl::StrFormat(
262 "symbol '%s' resolved to PC offset 0x%06X, which is too close to "
263 "the end of the ROM (%zu bytes) to hold a 32-word palette table",
264 label, pc, rom.
size()));
266 std::vector<uint16_t> table_words(32);
267 bool all_zero =
true;
268 for (
int i = 0; i < 32; ++i) {
269 table_words[i] = ReadWordLe(&raw[i * 2]);
270 if (table_words[i] != 0)
274 return absl::FailedPreconditionError(absl::StrFormat(
275 "symbol '%s' resolved to PC offset 0x%06X, but every byte there is "
276 "0. For Oracle of Secrets, labels like this are produced by "
277 "assembling the ASM sources and only exist in the *patched* ROM "
278 "(oos168x.sfc) -- the 'clean' base ROM a yaze project normally "
279 "points at (oos168.sfc) does not contain this data. Point this "
280 "source at the patched ROM, or use the HUD-palette or CGRAM-dump "
284 return BuildSubpaletteArray(table_words);
290 return absl::FailedPreconditionError(
291 "GameData::palette_groups.hud is empty; load a ROM/GameData first");
293 std::vector<uint16_t> table_words(32);
294 for (
int i = 0; i < 32; ++i) {
300 table_words[i] = color.
snes();
302 return BuildSubpaletteArray(table_words);
306 const std::string& path,
size_t byte_offset) {
307 std::ifstream in(path, std::ios::binary | std::ios::ate);
309 return absl::NotFoundError(
310 absl::StrFormat(
"could not open '%s' for reading", path));
312 std::streamsize size = in.tellg();
314 static_cast<size_t>(size) < byte_offset + 62 ) {
315 return absl::InvalidArgumentError(absl::StrFormat(
316 "'%s' is too small (%lld bytes) to hold 31 colors starting at byte "
318 path,
static_cast<long long>(size), byte_offset));
320 in.seekg(
static_cast<std::streamoff
>(byte_offset), std::ios::beg);
321 std::vector<uint8_t> raw(64);
322 size_t available =
static_cast<size_t>(size) - byte_offset;
323 size_t to_read = std::min<size_t>(64, available);
324 if (!in.read(
reinterpret_cast<char*
>(raw.data()),
325 static_cast<std::streamsize
>(to_read))) {
326 return absl::InternalError(absl::StrFormat(
"short read on '%s'", path));
328 std::vector<uint16_t> table_words(31, 0);
329 for (
size_t i = 0; i < table_words.size() && (i * 2 + 1) < to_read; ++i) {
330 table_words[i] = ReadWordLe(&raw[i * 2]);
332 return BuildSubpaletteArray(table_words);
340 const std::string& path) {
341 std::ifstream in(path, std::ios::binary | std::ios::ate);
343 return absl::NotFoundError(
344 absl::StrFormat(
"could not open '%s' for reading", path));
346 std::streamsize size = in.tellg();
348 return absl::InvalidArgumentError(absl::StrFormat(
"'%s' is empty", path));
350 in.seekg(0, std::ios::beg);
351 std::vector<uint8_t> raw(
static_cast<size_t>(size));
352 if (!in.read(
reinterpret_cast<char*
>(raw.data()), size)) {
353 return absl::InternalError(absl::StrFormat(
"short read on '%s'", path));
355 if (raw.size() % 16 != 0) {
356 return absl::InvalidArgumentError(absl::StrFormat(
357 "'%s' is %zu bytes, not a multiple of 16 (one 2bpp 8x8 tile is 16 "
364 constexpr size_t kChunkBytes = 0x800;
365 size_t num_chunks = (raw.size() + kChunkBytes - 1) / kChunkBytes;
366 std::vector<uint8_t> out;
367 out.reserve(num_chunks * 8192);
368 for (
size_t c = 0; c < num_chunks; ++c) {
369 size_t begin = c * kChunkBytes;
370 size_t end = std::min(raw.size(), begin + kChunkBytes);
371 std::vector<uint8_t> chunk(raw.begin() + begin, raw.begin() + end);
372 chunk.resize(kChunkBytes, 0);
374 out.insert(out.end(), converted.begin(), converted.end());
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< std::vector< uint8_t > > ReadByteVector(uint32_t offset, uint32_t length) const
Provider for symbol (label) resolution in disassembly.
std::optional< Symbol > FindSymbol(const std::string &name) const
Find symbol by name.
constexpr uint16_t snes() const
Get SNES 15-bit color.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
bool ParseHexString(absl::string_view str, uint64_t *out)
Zelda 3 specific classes and functions.
absl::StatusOr< std::array< gfx::SnesColor, 32 > > ResolveMenuPaletteFromHud(const GameData &game_data)
absl::StatusOr< std::vector< uint8_t > > Load2BppGraphics(const Rom &rom)
Loads 2BPP graphics sheets from ROM.
uint16_t Hexto555(uint32_t rgb)
absl::StatusOr< std::array< gfx::SnesColor, 32 > > ResolveMenuPaletteFromFile(const std::string &path, size_t byte_offset)
absl::StatusOr< std::vector< uint8_t > > ResolveMenuChrFromRom(const Rom &rom)
std::optional< std::string > FindMenuPaletteAsm(const std::string &project_dir, const std::string &code_folder)
absl::StatusOr< std::vector< uint16_t > > ParseMenuPaletteAsmText(std::string_view text_in, std::string_view label_in)
absl::StatusOr< std::vector< uint8_t > > ResolveMenuChrFromFile(const std::string &path)
absl::StatusOr< std::array< gfx::SnesColor, 32 > > ResolveMenuPaletteFromAsm(const std::string &path, const std::string &label)
absl::StatusOr< std::array< gfx::SnesColor, 32 > > ResolveMenuPaletteFromSymbol(const Rom &rom, const emu::debug::SymbolProvider &symbols, const std::string &label)
uint32_t SnesToPc(uint32_t addr) noexcept
SnesColor GetColor(int palette_index, int color_index) const
Get a specific color from a palette.
gfx::PaletteGroupMap palette_groups