diff --git a/arm9/Makefile b/arm9/Makefile index d464ce4..29651f4 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -15,8 +15,8 @@ include $(DEVKITARM)/ds_rules # all directories are relative to this makefile #--------------------------------------------------------------------------------- BUILD := build -SOURCES := src src/nand src/nand/polarssl src/nand/twltool -INCLUDES := include src/nand +SOURCES := src src/lzw src/nand src/nand/polarssl src/nand/twltool +INCLUDES := include src/nand src/lzw DATA := diff --git a/arm9/src/bgMenu.cpp b/arm9/src/bgMenu.cpp index dc916a7..75dca01 100644 --- a/arm9/src/bgMenu.cpp +++ b/arm9/src/bgMenu.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -51,7 +52,8 @@ std::span backgroundMenu() clearScreen(&topScreen); //menu - Menu* m = newMenu(); + auto mSptr = std::shared_ptr(newMenu(), freeMenu); + auto* m = mSptr.get(); setMenuHeader(m, "BACKGROUNDS"); const auto& bgs = getBackgroundList(); @@ -68,34 +70,44 @@ std::span backgroundMenu() //bottom screen printMenu(m); - while (!programEnd) - { - swiWaitForVBlank(); - scanKeys(); - - if (moveCursor(m)) - printMenu(m); - - if (auto keys = keysDown(); keys & KEY_A) - break; - else if(keys & KEY_B) + while (!programEnd) { + while (!programEnd) { - m->cursor = bgs.size() + 1; - break; + swiWaitForVBlank(); + scanKeys(); + + if (moveCursor(m)) + printMenu(m); + + if (auto keys = keysDown(); keys & KEY_A) + break; + else if(keys & KEY_B) + { + m->cursor = bgs.size() + 1; + break; + } } - } - auto selection = static_cast(m->cursor); - freeMenu(m); + auto selection = static_cast(m->cursor); - if(selection < bgs.size()) { + if(selection > bgs.size()) + return {}; try { - return parseGif(bgs[selection].second.data(), currentlyLoadedGif); + const auto res = parseGif(bgs[selection].second.data(), currentlyLoadedGif, bgGetGfxPtr(bgGifTop)); + bgHide(topScreen.bgId); + bgShow(bgGifTop); + auto confirmed = (choiceBox("Confirm this background?") == YES); + bgShow(topScreen.bgId); + bgHide(bgGifTop); + if(confirmed) + return res; } catch(const std::exception& e) { messageBox(std::format("\x1B[31mError:\x1B[33m The image could not\n" "be loaded: {}", e.what()).data()); } + + printMenu(m); } return {}; -} \ No newline at end of file +} diff --git a/arm9/src/gifConverter.cpp b/arm9/src/gifConverter.cpp index 6b8de6e..93d07a2 100644 --- a/arm9/src/gifConverter.cpp +++ b/arm9/src/gifConverter.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -10,6 +9,8 @@ #include "storage.h" #include "unlaunch.h" +#include "lzw/lzw.hpp" + struct Gif { struct Header { char signature[6]; @@ -24,7 +25,7 @@ struct Gif { } __attribute__ ((__packed__)) header; static_assert(sizeof(Header) == 13); - std::array colorTable; + std::array colorTable{}; size_t numColors; struct Frame { @@ -51,18 +52,18 @@ struct Gif { static constexpr auto WIDTH = 256; static constexpr auto HEIGHT = 192; -Gif getGif(std::string_view path) { +Gif getGif(std::string_view path, volatile uint16_t* decompressedData) { Gif gif; auto file = fopen(path.data(), "rb"); if (!file) throw std::runtime_error("Failed to open file"); auto fileSptr = std::shared_ptr(file, fclose); - - if(auto size = getFileSize(file); size < 7 || size > MAX_GIF_SIZE) - { - throw std::runtime_error("Gif file too big.\n"); - } + + if(auto size = getFileSize(file); size < 7 || size > MAX_GIF_SIZE) + { + throw std::runtime_error("Gif file too big.\n"); + } auto& header = gif.header; @@ -139,10 +140,29 @@ Gif getGif(std::string_view path) { } frame.image.lzwMinimumCodeSize = fgetc(file); + + LZWReader reader(frame.image.lzwMinimumCodeSize, [&, it = decompressedData](auto begin, auto end) mutable { + const auto ds_color_table = [&]{ + std::array ret{}; + for(int i = 0; i < numColors; ++i){ + auto r = color_table[(i * 3) + 0] >> 3; + auto g = color_table[(i * 3) + 1] >> 3; + auto b = color_table[(i * 3) + 2] >> 3; + ret[i] = RGB15(r,g,b) | BIT(15); + } + return ret; + }(); + std::transform(begin, end, it, [&](auto idx) { + return ds_color_table[idx]; + }); + it += std::distance(begin, end); + }); + while (uint8_t size = fgetc(file)) { std::vector dataChunk; dataChunk.resize(size); fread(dataChunk.data(), 1, size, file); + reader.decode(dataChunk.begin(), dataChunk.end()); frame.image.imageDataChunks.push_back(std::move(dataChunk)); } @@ -199,7 +219,7 @@ std::span writeGif(const Gif& gif, std::array& o } -std::span parseGif(const char* path, std::array& outArr) { - const auto gif = getGif(path); - return writeGif(gif, outArr); +std::span parseGif(const char* path, std::array& outArr, volatile uint16_t* decompressedData) { + const auto gif = getGif(path, decompressedData); + return writeGif(gif, outArr); } diff --git a/arm9/src/gifConverter.h b/arm9/src/gifConverter.h index eb6eaf9..4916980 100644 --- a/arm9/src/gifConverter.h +++ b/arm9/src/gifConverter.h @@ -7,6 +7,6 @@ #include "unlaunch.h" -std::span parseGif(const char* path, std::array& outArr); +std::span parseGif(const char* path, std::array& outArr, volatile uint16_t* decompressedData); -#endif //GIF_CONVERTER_H \ No newline at end of file +#endif //GIF_CONVERTER_H diff --git a/arm9/src/lzw/lzw.cpp b/arm9/src/lzw/lzw.cpp new file mode 100644 index 0000000..f21ab01 --- /dev/null +++ b/arm9/src/lzw/lzw.cpp @@ -0,0 +1,125 @@ +#include "lzw.hpp" + +u16 LZWReader::readLSB(std::vector::iterator &begin, const std::vector::iterator &end) { + while (nBits < width) { + if (begin == end) { + err = true; + return 0; + } + u8 x = *(begin++); + bits |= x << nBits; + nBits += 8; + } + u16 code = bits & ((1 << width) - 1); + bits >>= width; + nBits -= width; + return code; +} + +bool LZWReader::decode(std::vector::iterator begin, std::vector::iterator end) { + o = 0; + err = false; + // Loop over the code stream, converting codes into decompressed bytes. + while (begin != end) { + u16 code = readLSB(begin, end); + if (err) { + flush(); + return false; + } + + if (code < clear) { // Literal + output[o++] = code; + if (last != DECODER_INVALID_CODE) { + // Save what the hi code expands to. + suffix[hi] = code; + prefix[hi] = last; + } + } else if (code == clear) { // Clear + width = 1 + litWidth; + hi = eof; + overflow = 1 << width; + last = DECODER_INVALID_CODE; + continue; + } else if (code == eof) { // End + flush(); + return true; + } else if (code <= hi) { + u16 c = code; + uint i = output.size() - 1; + if (code == hi && last != DECODER_INVALID_CODE) { + // code == hi is a special case which expands to the last expansion + // followed by the head of the last expansion. To find the head, we walk + // the prefix chain until we find a literal code. + c = last; + while (c >= clear) + c = prefix[c]; + output[i] = c; + i--; + c = last; + } + // Copy the suffix chain into output and then write that to w. + while (c >= clear) { + output[i] = suffix[c]; + i--; + c = prefix[c]; + } + output[i] = c; + std::copy(output.begin() + i, output.end(), output.begin() + o); + o += std::distance(output.begin() + i, output.end()); + if (last != DECODER_INVALID_CODE) { + // Save what the hi code expands to + suffix[hi] = c; + prefix[hi] = last; + } + } else { // Error + flush(); + return false; + } + + last = code; + hi++; + if (hi >= overflow) { + if (hi > overflow) { + flush(); + return false; + } + + if (width == MAX_WIDTH) { + last = DECODER_INVALID_CODE; + // Undo the d.hi++ a few lines above, so that (1) we maintain + // the invariant that d.hi < d.overflow, and (2) d.hi does not + // eventually overflow a uint16. + hi--; + } else { + width++; + overflow = 1 << width; + } + } + if (o >= FLUSH_BUFFER) { + flush(); + } + } + + flush(); + return true; +} + +LZWReader::LZWReader(int minCodeSize, std::function flushFunction) : litWidth(minCodeSize), flushFn(flushFunction) { + width = 1 + litWidth; + clear = 1 << litWidth; + eof = clear + 1; + hi = clear + 1; + overflow = 1 << width; + last = DECODER_INVALID_CODE; + + suffix = std::vector(1 << MAX_WIDTH); + prefix = std::vector(1 << MAX_WIDTH); + output = std::vector(2 * (1 << MAX_WIDTH)); +} + +void LZWReader::flush(void) { + if (flushFn && o > 0) { + flushFn(output.begin(), output.begin() + o); + } + o = 0; +} diff --git a/arm9/src/lzw/lzw.hpp b/arm9/src/lzw/lzw.hpp new file mode 100644 index 0000000..b2506b5 --- /dev/null +++ b/arm9/src/lzw/lzw.hpp @@ -0,0 +1,44 @@ +#ifndef LZW_HPP +#define LZW_HPP + +#include +#include +#include + +typedef unsigned int uint; +typedef std::vector::const_iterator u8_itr; + +class LZWReader { + constexpr static u16 MAX_WIDTH = 12; + constexpr static u16 DECODER_INVALID_CODE = 0xFFFF; + constexpr static u16 FLUSH_BUFFER = 1 << MAX_WIDTH; + + int litWidth; + std::function flushFn; + u32 bits = 0; + uint nBits = 0; + uint width; + bool err = false; + + u16 clear, eof, hi, overflow, last; + + std::vector suffix; + std::vector prefix; + + std::vector output; + int o = 0; + // std::vector toRead; + + u16 readLSB(std::vector::iterator &it, const std::vector::iterator &end); + + int read(std::vector &buffer); + + void flush(void); + +public: + LZWReader(int minCodeSize, std::function flushFunction); + + bool decode(std::vector::iterator begin, std::vector::iterator end); +}; + +#endif diff --git a/arm9/src/main.cpp b/arm9/src/main.cpp index 4969062..a498d32 100644 --- a/arm9/src/main.cpp +++ b/arm9/src/main.cpp @@ -35,6 +35,9 @@ static bool isLauncherVersionSupported = true; PrintConsole topScreen; PrintConsole bottomScreen; +int bgGifTop; +int bgGifBottom; + struct Stage2 { Sha1Digest sha; bool unlaunch_supported; @@ -71,20 +74,22 @@ enum { static void setupScreens() { - REG_DISPCNT = MODE_FB0; - VRAM_A_CR = VRAM_ENABLE; - - videoSetMode(MODE_0_2D); - videoSetModeSub(MODE_0_2D); + videoSetMode(MODE_5_2D); + videoSetModeSub(MODE_5_2D); vramSetBankA(VRAM_A_MAIN_BG); vramSetBankC(VRAM_C_SUB_BG); - consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true); - consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true); + consoleInit(&topScreen, 1, BgType_Text4bpp, BgSize_T_256x256, 14, 0, true, true); + consoleInit(&bottomScreen, 1, BgType_Text4bpp, BgSize_T_256x256, 14, 0, false, true); clearScreen(&bottomScreen); + bgGifTop = bgInit(3, BgType_Bmp16, BgSize_B16_256x256, 2, 0); + bgHide(bgGifTop); + bgGifBottom = bgInitSub(3, BgType_Bmp16, BgSize_B16_256x256, 2, 0); + bgHide(bgGifBottom); + VRAM_A[100] = 0xFFFF; } diff --git a/arm9/src/main.h b/arm9/src/main.h index b86ee00..fe70da9 100644 --- a/arm9/src/main.h +++ b/arm9/src/main.h @@ -16,10 +16,13 @@ extern volatile u8 batteryLevel; extern PrintConsole topScreen; extern PrintConsole bottomScreen; +extern int bgGifTop; +extern int bgGifBottom; + void clearScreen(PrintConsole* screen); #ifdef __cplusplus } #endif -#endif \ No newline at end of file +#endif