summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkira Akashi <rubenru09@aol.com>2021-08-22 12:31:00 +0100
committerGitHub <noreply@github.com>2021-08-22 12:31:00 +0100
commit28b66f4e9efc2dbde3005f3e5bcc44b93ae47796 (patch)
tree6078bb064621ff03fbcb8ddec952eb05730c8e8c
parent97c8e77b2f951b6f0c43933576f21b833e7d5876 (diff)
parentd08722a7381c4c05a40ee59bb6de556616e1dfc2 (diff)
Merge branch 'master' into master
-rw-r--r--Makefile4
-rw-r--r--tools/nitrobanner/.gitignore1
-rw-r--r--tools/nitrobanner/LICENSE21
-rw-r--r--tools/nitrobanner/Makefile30
-rw-r--r--tools/nitrobanner/banner.cpp276
-rw-r--r--tools/nitrobanner/banner.h61
-rw-r--r--tools/nitrobanner/crc16.cpp22
-rw-r--r--tools/nitrobanner/crc16.h5
-rw-r--r--tools/nitrobanner/main.cpp45
-rw-r--r--tools/nitrobanner/types.h15
10 files changed, 478 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index c9be64f8..9cb3187b 100644
--- a/Makefile
+++ b/Makefile
@@ -90,6 +90,7 @@ KNARC = tools/knarc/knarc$(EXE)
MSGENC = tools/msgenc/msgenc$(EXE)
MWLDARM = tools/mwccarm/$(MWCCVERSION)/mwldarm.exe
MWASMARM = tools/mwccarm/$(MWCCVERSION)/mwasmarm.exe
+NITROBANNER = tools/nitrobanner/nitrobanner$(EXE)
SCANINC = tools/scaninc/scaninc$(EXE)
AS = $(WINE) $(MWASMARM)
@@ -119,7 +120,6 @@ JSONPROC = $(TOOLS_DIR)/jsonproc/jsonproc$(EXE)
O2NARC = $(TOOLS_DIR)/o2narc/o2narc$(EXE)
GFX = $(TOOLS_DIR)/nitrogfx/nitrogfx$(EXE)
MWASMARM_PATCHER = $(TOOLS_DIR)/mwasmarm_patcher/mwasmarm_patcher$(EXE) -q
-MAKEBANNER = $(WINE) $(TOOLS_DIR)/bin/makebanner.exe
MAKEROM = $(WINE) $(TOOLS_DIR)/bin/makerom.exe
FIXROM = $(TOOLS_DIR)/fixrom/fixrom$(EXE)
NTRCOMP = $(WINE) $(TOOLS_DIR)/bin/ntrcomp.exe
@@ -286,7 +286,7 @@ $(8BPP_COMP10_NOPAD_NCLR_PAL_FILES): GFX_FLAGS = -bitdepth 8 -nopad -comp 10
######################## Misc #######################
$(BNR): $(TARGET).bsf $(ICON_FILE:%.png=%.gbapal) $(ICON_FILE:%.png=%.4bpp)
- $(MAKEBANNER) $< $@
+ $(NITROBANNER) $< $@
symbols.csv: arm9 arm7
(echo "Name,Location"; $(GREP) " *[0-9A-F]{8} [0-9A-F]{8} \S+ +\w+\t\(\w+\.o\)" arm9/$(BUILD_DIR)/arm9.elf.xMAP arm7/build/arm7.elf.xMAP | $(SED) 's/ *([0-9A-F]{8}) [0-9A-F]{8} \S+ +(\w+)\t\(\w+\.o\)/\2,\1/g' | cut -d: -f2) > $@
diff --git a/tools/nitrobanner/.gitignore b/tools/nitrobanner/.gitignore
new file mode 100644
index 00000000..e8eff425
--- /dev/null
+++ b/tools/nitrobanner/.gitignore
@@ -0,0 +1 @@
+nitrobanner
diff --git a/tools/nitrobanner/LICENSE b/tools/nitrobanner/LICENSE
new file mode 100644
index 00000000..193cec26
--- /dev/null
+++ b/tools/nitrobanner/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2021 tgsm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/tools/nitrobanner/Makefile b/tools/nitrobanner/Makefile
new file mode 100644
index 00000000..322525bd
--- /dev/null
+++ b/tools/nitrobanner/Makefile
@@ -0,0 +1,30 @@
+CXXFLAGS := -std=c++17 -O3 -Wall -Wextra -Wpedantic
+
+ifeq ($(OS),Windows_NT)
+LDFLAGS += -lstdc++fs
+else
+UNAME_S := $(shell uname -s)
+ifeq ($(UNAME_S),Darwin)
+LDFLAGS += -lstdc++ -lc++ -lc /usr/local/opt/llvm@8/lib/libc++fs.a
+else
+LDFLAGS += -lstdc++fs
+endif
+endif
+
+OBJS = \
+ banner.o \
+ crc16.o \
+ main.o
+
+.PHONY: all clean
+
+all: nitrobanner
+
+%.o: %.cpp
+ $(CXX) -c -o $@ $< $(CXXFLAGS)
+
+clean:
+ $(RM) nitrobanner nitrobanner.exe *.o
+
+nitrobanner: $(OBJS)
+ $(CXX) -o $@ $^ $(LDFLAGS)
diff --git a/tools/nitrobanner/banner.cpp b/tools/nitrobanner/banner.cpp
new file mode 100644
index 00000000..ef73ccf9
--- /dev/null
+++ b/tools/nitrobanner/banner.cpp
@@ -0,0 +1,276 @@
+#include <cstdio>
+#include <codecvt>
+#include <exception>
+#include <fstream>
+#include <locale>
+#include <string>
+#include "banner.h"
+#include "crc16.h"
+
+template <typename T>
+void SerializeData(std::ofstream& ostream, T data) {
+ for (std::size_t i = 0; i < sizeof(T); i++) {
+ ostream.put(static_cast<u8>((data >> (i * 8)) & 0xFF));
+ }
+}
+
+std::wstring GetTitleAndDeveloperFromSpecCommandArgument(std::wifstream& stream, const std::wstring& command_argument) {
+ std::wstring title_and_developer = command_argument;
+ constexpr int max_lines = 3;
+ int current_line = 1;
+
+ while (true) {
+ wchar_t first_char = stream.get();
+ stream.unget();
+ if (first_char != L' ') {
+ return title_and_developer;
+ }
+
+ if (current_line++ == max_lines) {
+ throw std::runtime_error("Title and developer can not be more than " + std::to_string(max_lines) + " lines");
+ }
+
+ std::wstring line;
+ std::getline(stream, line);
+
+ title_and_developer += '\n';
+ title_and_developer += line.substr(line.find_first_not_of(L' '), line.size());
+ }
+}
+
+SpecFileData ParseSpecFile(const filesystem::path& specfile_path) {
+ std::wifstream stream(specfile_path, std::ios::binary);
+ if (!stream.is_open()) {
+ throw std::runtime_error("could not open specfile " + specfile_path.string() + " for parsing");
+ }
+
+ // convert utf-16 to utf-32
+ stream.imbue(std::locale(stream.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>));
+
+ // first character of the file has to be U+FEFF.
+ if (stream.get() != 0xFEFF) {
+ throw std::runtime_error("specfile must be encoded as UTF-16");
+ }
+
+ SpecFileData specfile_data = {};
+
+ for (std::wstring line; std::getline(stream, line);) {
+ const std::size_t end_of_command_name = line.find(L':');
+ if (end_of_command_name == std::wstring::npos) {
+ continue;
+ }
+
+ const std::size_t beginning_of_command_name = line.find_first_not_of(' ');
+ const std::wstring command_name = line.substr(beginning_of_command_name, end_of_command_name - beginning_of_command_name);
+
+ const std::size_t beginning_of_command_argument = line.find_first_of(L": ") + 2;
+ const std::wstring command_argument = line.substr(beginning_of_command_argument, line.size());
+
+ if (command_name == L"Version") {
+ printf("warning: Version command is currently unsupported, defaulting to version 1\n");
+ specfile_data.version = 1;
+ } else if (command_name == L"ImageFile") {
+ specfile_data.icon_bitmap_filename = command_argument;
+ } else if (command_name == L"PlttFile") {
+ specfile_data.icon_palette_filename = command_argument;
+ } else if (CommandIsForTitleAndDeveloper(command_name)) {
+ const std::wstring& title_and_developer = GetTitleAndDeveloperFromSpecCommandArgument(stream, command_argument);
+
+ if (command_name == L"JP") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.japanese_title += char16_t(c);
+ }
+ } else if (command_name == L"EN") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.english_title += char16_t(c);
+ }
+ } else if (command_name == L"FR") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.french_title += char16_t(c);
+ }
+ } else if (command_name == L"GE") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.german_title += char16_t(c);
+ }
+ } else if (command_name == L"IT") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.italian_title += char16_t(c);
+ }
+ } else if (command_name == L"SP") {
+ for (wchar_t c : title_and_developer) {
+ specfile_data.spanish_title += char16_t(c);
+ }
+ }
+ } else {
+ printf("warning: unsupported command '%ls', ignoring...\n", command_name.data());
+ }
+ }
+
+ if (specfile_data.version == 0) {
+ // no banner version provided, assuming version 1
+ specfile_data.version = 1;
+ }
+
+ if (specfile_data.icon_bitmap_filename.empty()) {
+ throw std::runtime_error("missing required ImageFile command (filename of icon bitmap)");
+ }
+
+ if (specfile_data.icon_palette_filename.empty()) {
+ throw std::runtime_error("missing required PlttFile command (filename of icon palette)");
+ }
+
+ if (specfile_data.japanese_title.empty()) {
+ throw std::runtime_error("missing required JP command (Japanese title & developer)");
+ }
+
+ if (specfile_data.english_title.empty()) {
+ throw std::runtime_error("missing required EN command (English title & developer)");
+ }
+
+ if (specfile_data.french_title.empty()) {
+ throw std::runtime_error("missing required FR command (French title & developer)");
+ }
+
+ if (specfile_data.german_title.empty()) {
+ throw std::runtime_error("missing required GE command (German title & developer)");
+ }
+
+ if (specfile_data.italian_title.empty()) {
+ throw std::runtime_error("missing required IT command (Italian title & developer)");
+ }
+
+ if (specfile_data.spanish_title.empty()) {
+ throw std::runtime_error("missing required SP command (Spanish title & developer)");
+ }
+
+ return specfile_data;
+}
+
+IconBitmap GetIconBitmap(const filesystem::path& icon_bitmap_filename) {
+ if (!filesystem::is_regular_file(icon_bitmap_filename)) {
+ throw std::runtime_error("icon bitmap file '" + icon_bitmap_filename.string() + "' does not exist / is not a regular file");
+ }
+
+ if (filesystem::file_size(icon_bitmap_filename) != ICON_BITMAP_SIZE) {
+ throw std::runtime_error("icon bitmap is not 512 bytes");
+ }
+
+ std::ifstream stream(icon_bitmap_filename, std::ios::binary);
+ if (!stream.is_open()) {
+ throw std::runtime_error("could not open icon bitmap file '" + icon_bitmap_filename.string() + "' for reading");
+ }
+
+ IconBitmap bitmap = {};
+ stream.read(reinterpret_cast<char*>(bitmap.data()), ICON_BITMAP_SIZE);
+ return bitmap;
+}
+
+IconPalette GetIconPalette(const filesystem::path& icon_palette_filename) {
+ if (!filesystem::is_regular_file(icon_palette_filename)) {
+ throw std::runtime_error("icon palette file '" + icon_palette_filename.string() + "' does not exist / is not a regular file");
+ }
+
+ if (filesystem::file_size(icon_palette_filename) != ICON_PALETTE_SIZE) {
+ throw std::runtime_error("icon palette is not 32 bytes");
+ }
+
+ std::ifstream stream(icon_palette_filename, std::ios::binary);
+ if (!stream.is_open()) {
+ throw std::runtime_error("could not open icon palette file '" + icon_palette_filename.string() + "' for reading");
+ }
+
+ IconPalette palette = {};
+ stream.read(reinterpret_cast<char*>(palette.data()), ICON_PALETTE_SIZE);
+ return palette;
+}
+
+void OutputBanner(std::ofstream& ostream, const Banner& banner) {
+ SerializeData<u16>(ostream, banner.version);
+ SerializeData<u16>(ostream, banner.crc);
+
+ for ([[maybe_unused]] u16 i : banner.crc_padding) {
+ SerializeData<u16>(ostream, 0);
+ }
+
+ for ([[maybe_unused]] u8 i : banner.padding) {
+ SerializeData<u8>(ostream, 0);
+ }
+
+ for (u8 i : banner.bitmap) {
+ SerializeData<u8>(ostream, i);
+ }
+
+ for (u8 i : banner.palette) {
+ SerializeData<u8>(ostream, i);
+ }
+
+ for (char16_t c : banner.japanese_title) {
+ SerializeData<u16>(ostream, c);
+ }
+
+ for (char16_t c : banner.english_title) {
+ SerializeData<u16>(ostream, c);
+ }
+
+ for (char16_t c : banner.french_title) {
+ SerializeData<u16>(ostream, c);
+ }
+
+ for (char16_t c : banner.german_title) {
+ SerializeData<u16>(ostream, c);
+ }
+
+ for (char16_t c : banner.italian_title) {
+ SerializeData<u16>(ostream, c);
+ }
+
+ for (char16_t c : banner.spanish_title) {
+ SerializeData<u16>(ostream, c);
+ }
+}
+
+bool MakeBanner(const filesystem::path& specfile_path, const filesystem::path& outfile_path) {
+ std::ofstream ostream(outfile_path, std::ios::binary);
+ if (!ostream.is_open()) {
+#ifdef _MSC_VER
+ printf("error: could not open %ls for writing\n", outfile_path.c_str());
+#else
+ printf("error: could not open %s for writing\n", outfile_path.c_str());
+#endif
+ return false;
+ }
+
+ SpecFileData specfile_data = {};
+ try {
+ specfile_data = ParseSpecFile(specfile_path);
+ } catch (std::runtime_error& e) {
+ printf("error: %s\n", e.what());
+ return false;
+ }
+
+ Banner banner = {};
+
+ banner.version = specfile_data.version;
+ banner.bitmap = GetIconBitmap(specfile_data.icon_bitmap_filename);
+ banner.palette = GetIconPalette(specfile_data.icon_palette_filename);
+
+ std::copy(specfile_data.japanese_title.begin(), specfile_data.japanese_title.end(), banner.japanese_title.data());
+ std::copy(specfile_data.english_title.begin(), specfile_data.english_title.end(), banner.english_title.data());
+ std::copy(specfile_data.french_title.begin(), specfile_data.french_title.end(), banner.french_title.data());
+ std::copy(specfile_data.german_title.begin(), specfile_data.german_title.end(), banner.german_title.data());
+ std::copy(specfile_data.italian_title.begin(), specfile_data.italian_title.end(), banner.italian_title.data());
+ std::copy(specfile_data.spanish_title.begin(), specfile_data.spanish_title.end(), banner.spanish_title.data());
+
+ // checksum the banner data, starting from the icon bitmap and ending at the end of the file.
+ banner.crc = CalculateCRC16FromBannerData(banner.bitmap.data());
+
+ // check against diamond's icon crc
+ // if (banner.crc != 0x048B) {
+ // printf("CRC did not match (expected 0x048B, got 0x%04X)\n", banner.crc);
+ // } else {
+ // printf("CRC matched\n");
+ // }
+
+ OutputBanner(ostream, banner);
+ return true;
+}
diff --git a/tools/nitrobanner/banner.h b/tools/nitrobanner/banner.h
new file mode 100644
index 00000000..70684172
--- /dev/null
+++ b/tools/nitrobanner/banner.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <array>
+#include <filesystem>
+#include "types.h"
+
+constexpr int TITLE_LENGTH = 0x100 / sizeof(u16); // 128 UTF-16 characters
+constexpr int ICON_BITMAP_SIZE = 0x200; // 512 bytes
+constexpr int ICON_PALETTE_SIZE = 0x20; // 32 bytes
+
+struct SpecFileData {
+ u16 version;
+ std::wstring icon_bitmap_filename;
+ std::wstring icon_palette_filename;
+ std::u16string japanese_title;
+ std::u16string english_title;
+ std::u16string french_title;
+ std::u16string german_title;
+ std::u16string italian_title;
+ std::u16string spanish_title;
+};
+
+using IconBitmap = std::array<u8, ICON_BITMAP_SIZE>;
+using IconPalette = std::array<u8, ICON_PALETTE_SIZE>;
+
+struct Banner {
+ u16 version;
+ u16 crc;
+ const std::array<u16, 3> crc_padding {};
+ const std::array<u8, 0x16> padding {};
+ IconBitmap bitmap {};
+ IconPalette palette {};
+ std::array<u16, TITLE_LENGTH> japanese_title {};
+ std::array<u16, TITLE_LENGTH> english_title {};
+ std::array<u16, TITLE_LENGTH> french_title {};
+ std::array<u16, TITLE_LENGTH> german_title {};
+ std::array<u16, TITLE_LENGTH> italian_title {};
+ std::array<u16, TITLE_LENGTH> spanish_title {};
+};
+static_assert(sizeof(Banner) == 0x840, "Size of banner struct is wrong");
+
+template <typename T>
+void SerializeData(std::ofstream& ostream, T data);
+
+void ProcessSpecFileCommand(SpecFileData& specfile_data, const std::wstring_view& command_name, const std::wstring_view& command_argument);
+SpecFileData ParseSpecFile(const std::filesystem::path& specfile_path);
+
+IconBitmap GetIconBitmap(const std::filesystem::path& icon_bitmap_filename);
+IconPalette GetIconPalette(const std::filesystem::path& icon_palette_filename);
+
+void OutputBanner(std::ofstream& ostream, const Banner& banner);
+bool MakeBanner(const std::filesystem::path& specfile_path, const std::filesystem::path& outfile_path);
+
+inline bool CommandIsForTitleAndDeveloper(const std::wstring& command_name) {
+ return command_name == L"JP" ||
+ command_name == L"EN" ||
+ command_name == L"FR" ||
+ command_name == L"GE" ||
+ command_name == L"IT" ||
+ command_name == L"SP";
+}
diff --git a/tools/nitrobanner/crc16.cpp b/tools/nitrobanner/crc16.cpp
new file mode 100644
index 00000000..bac64b57
--- /dev/null
+++ b/tools/nitrobanner/crc16.cpp
@@ -0,0 +1,22 @@
+#include <array>
+#include "banner.h"
+#include "crc16.h"
+
+u16 CalculateCRC16FromBannerData(const u8* banner_data) {
+ const std::array<u16, 16> crc_table = {
+ 0x0000, 0xCC01, 0xD801, 0x1400,
+ 0xF001, 0x3C00, 0x2800, 0xE401,
+ 0xA001, 0x6C00, 0x7800, 0xB401,
+ 0x5000, 0x9C01, 0x8801, 0x4400,
+ };
+
+ u16 checksum = 0xFFFF;
+
+ const std::size_t data_size = sizeof(Banner) - offsetof(Banner, bitmap);
+ for (std::size_t i = 0; i < data_size; i++) {
+ u16 lookup_index = crc_table[banner_data[i] & 0xF] ^ (checksum >> 4) ^ crc_table[checksum & 0xF];
+ checksum = crc_table[banner_data[i] >> 4] ^ (lookup_index >> 4) ^ crc_table[lookup_index & 0xF];
+ }
+
+ return checksum;
+}
diff --git a/tools/nitrobanner/crc16.h b/tools/nitrobanner/crc16.h
new file mode 100644
index 00000000..29811cf9
--- /dev/null
+++ b/tools/nitrobanner/crc16.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "types.h"
+
+u16 CalculateCRC16FromBannerData(const u8* banner_data);
diff --git a/tools/nitrobanner/main.cpp b/tools/nitrobanner/main.cpp
new file mode 100644
index 00000000..031d9218
--- /dev/null
+++ b/tools/nitrobanner/main.cpp
@@ -0,0 +1,45 @@
+#include <cstdio>
+#include <cstring>
+#include "banner.h"
+#include "types.h"
+
+#ifdef _MSC_VER
+#define strcasecmp _stricmp
+#endif
+
+int main(int argc, char* argv[]) {
+ if (argc != 2 && argc != 3) {
+ printf("usage: %s <specfile> [outfile]\n", argv[0]);
+ return 1;
+ }
+
+ const filesystem::path specfile_path = argv[1];
+ if (!filesystem::is_regular_file(specfile_path)) {
+ printf("error: provided specfile does not exist / is not a regular file. (did you put the right path?)\n");
+ return 1;
+ }
+
+ if (specfile_path.extension() == ".bnr") {
+ printf("error: can't use a bnr file as a specfile\n");
+ return 1;
+ }
+
+ // If the user doesn't provide a path to an outfile, or if the provided outfile is
+ // identical to the provided specfile, use the specfile's name + the .bnr extension.
+ filesystem::path outfile_path;
+ if (argc == 2 || strcasecmp(argv[1], argv[2]) == 0) {
+ outfile_path = specfile_path.stem().string() + ".bnr";
+ } else {
+ outfile_path = argv[2];
+ }
+
+ // printf("debug: specfile: %s\n", specfile_path.c_str());
+ // printf("debug: outfile: %s\n", outfile_path.c_str());
+
+ if (!MakeBanner(specfile_path, outfile_path)) {
+ printf("error: failed to create banner file\n");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/tools/nitrobanner/types.h b/tools/nitrobanner/types.h
new file mode 100644
index 00000000..5776e62f
--- /dev/null
+++ b/tools/nitrobanner/types.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <cstdint>
+
+#if (__GNUC__ <= 7) && !defined _MSC_VER
+#include <experimental/filesystem>
+namespace filesystem = std::experimental::filesystem;
+#else
+#include <filesystem>
+namespace filesystem = std::filesystem;
+#endif
+
+using u8 = uint8_t;
+using u16 = uint16_t;
+using u32 = uint32_t;