Show background preview when selecting it

This commit is contained in:
Edoardo Lolletti 2025-08-21 00:07:17 +02:00
parent 58a2ac164f
commit e88dc13af8
8 changed files with 252 additions and 43 deletions

View File

@ -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 :=

View File

@ -9,6 +9,7 @@
#include <format>
#include <nds.h>
#include <dirent.h>
#include <memory>
#include <string>
#include <vector>
@ -51,7 +52,8 @@ std::span<uint8_t> backgroundMenu()
clearScreen(&topScreen);
//menu
Menu* m = newMenu();
auto mSptr = std::shared_ptr<Menu>(newMenu(), freeMenu);
auto* m = mSptr.get();
setMenuHeader(m, "BACKGROUNDS");
const auto& bgs = getBackgroundList();
@ -68,34 +70,44 @@ std::span<uint8_t> 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<size_t>(m->cursor);
freeMenu(m);
auto selection = static_cast<size_t>(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 {};
}
}

View File

@ -2,7 +2,6 @@
#include <cstring>
#include <cstdio>
#include <cstdint>
#include <format>
#include <memory>
#include <stdexcept>
#include <vector>
@ -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<uint8_t, 256 * 3> colorTable;
std::array<uint8_t, 256 * 3> 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>(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<uint16_t, 256> 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<uint8_t> 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<uint8_t> writeGif(const Gif& gif, std::array<uint8_t, MAX_GIF_SIZE>& o
}
std::span<uint8_t> parseGif(const char* path, std::array<uint8_t, MAX_GIF_SIZE>& outArr) {
const auto gif = getGif(path);
return writeGif(gif, outArr);
std::span<uint8_t> parseGif(const char* path, std::array<uint8_t, MAX_GIF_SIZE>& outArr, volatile uint16_t* decompressedData) {
const auto gif = getGif(path, decompressedData);
return writeGif(gif, outArr);
}

View File

@ -7,6 +7,6 @@
#include "unlaunch.h"
std::span<uint8_t> parseGif(const char* path, std::array<uint8_t, MAX_GIF_SIZE>& outArr);
std::span<uint8_t> parseGif(const char* path, std::array<uint8_t, MAX_GIF_SIZE>& outArr, volatile uint16_t* decompressedData);
#endif //GIF_CONVERTER_H
#endif //GIF_CONVERTER_H

125
arm9/src/lzw/lzw.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "lzw.hpp"
u16 LZWReader::readLSB(std::vector<u8>::iterator &begin, const std::vector<u8>::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<u8>::iterator begin, std::vector<u8>::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<void(u8_itr, u8_itr)> 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<u8>(1 << MAX_WIDTH);
prefix = std::vector<u16>(1 << MAX_WIDTH);
output = std::vector<u8>(2 * (1 << MAX_WIDTH));
}
void LZWReader::flush(void) {
if (flushFn && o > 0) {
flushFn(output.begin(), output.begin() + o);
}
o = 0;
}

44
arm9/src/lzw/lzw.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef LZW_HPP
#define LZW_HPP
#include <nds.h>
#include <functional>
#include <vector>
typedef unsigned int uint;
typedef std::vector<u8>::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<void(u8_itr, u8_itr)> flushFn;
u32 bits = 0;
uint nBits = 0;
uint width;
bool err = false;
u16 clear, eof, hi, overflow, last;
std::vector<u8> suffix;
std::vector<u16> prefix;
std::vector<u8> output;
int o = 0;
// std::vector<u8> toRead;
u16 readLSB(std::vector<u8>::iterator &it, const std::vector<u8>::iterator &end);
int read(std::vector<u8> &buffer);
void flush(void);
public:
LZWReader(int minCodeSize, std::function<void(u8_itr, u8_itr)> flushFunction);
bool decode(std::vector<u8>::iterator begin, std::vector<u8>::iterator end);
};
#endif

View File

@ -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;
}

View File

@ -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
#endif