mirror of
https://github.com/rvtr/unlaunch-installer_dev.git
synced 2026-01-26 13:43:08 -05:00
Add sanitization to loaded GIF images
Parse the provided loaded background images and reconstruct a new GIF file on the fly to ensure they don't contain any extension that might not be supported by unalunch. The generated GIF file will have a global color table, and single section, being the image descriptor. It also won't have any looping block.
This commit is contained in:
parent
5bfa4b1a00
commit
6edcde25c3
@ -1,13 +1,19 @@
|
||||
#include "bgMenu.h"
|
||||
#include "main.h"
|
||||
#include "menu.h"
|
||||
#include "gifConverter.h"
|
||||
#include "message.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <format>
|
||||
#include <nds.h>
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static std::array<uint8_t, MAX_GIF_SIZE> currentlyLoadedGif;
|
||||
|
||||
static const auto& getBackgroundList()
|
||||
{
|
||||
static auto bgs = []{
|
||||
@ -39,7 +45,7 @@ static const auto& getBackgroundList()
|
||||
return bgs;
|
||||
}
|
||||
|
||||
const char* backgroundMenu()
|
||||
std::span<uint8_t> backgroundMenu()
|
||||
{
|
||||
//top screen
|
||||
clearScreen(&topScreen);
|
||||
@ -79,12 +85,17 @@ const char* backgroundMenu()
|
||||
}
|
||||
}
|
||||
|
||||
const char* result = nullptr;
|
||||
if(static_cast<size_t>(m->cursor) < bgs.size())
|
||||
result = bgs[m->cursor].second.data();
|
||||
else if(static_cast<size_t>(m->cursor) == bgs.size())
|
||||
result = "default";
|
||||
auto selection = static_cast<size_t>(m->cursor);
|
||||
freeMenu(m);
|
||||
|
||||
return result;
|
||||
if(selection < bgs.size()) {
|
||||
try {
|
||||
return parseGif(bgs[selection].second.data(), currentlyLoadedGif);
|
||||
} catch(const std::exception& e) {
|
||||
messageBox(std::format("\x1B[31mError:\x1B[33m The image could not\n"
|
||||
"be loaded: {}", e.what()).data());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -1,14 +1,9 @@
|
||||
#ifndef BGMENU_H
|
||||
#define BGMENU_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
const char* backgroundMenu();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
std::span<uint8_t> backgroundMenu();
|
||||
|
||||
#endif
|
||||
225
arm9/src/gifConverter.cpp
Normal file
225
arm9/src/gifConverter.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "storage.h"
|
||||
#include "unlaunch.h"
|
||||
|
||||
struct Gif {
|
||||
struct Header {
|
||||
char signature[6];
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t gctSize: 3;
|
||||
uint8_t sortFlag: 1;
|
||||
uint8_t colorResolution: 3;
|
||||
uint8_t gctFlag: 1;
|
||||
uint8_t bgColor;
|
||||
uint8_t pixelAspectRatio;
|
||||
} __attribute__ ((__packed__)) header;
|
||||
static_assert(sizeof(Header) == 13);
|
||||
|
||||
std::array<uint8_t, 255 * 3> colorTable;
|
||||
size_t numColors;
|
||||
|
||||
struct Frame {
|
||||
struct Descriptor {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t w;
|
||||
uint16_t h;
|
||||
uint8_t lctSize: 3;
|
||||
uint8_t reserved: 2;
|
||||
uint8_t sortFlag: 1;
|
||||
uint8_t interlaceFlag: 1;
|
||||
uint8_t lctFlag: 1;
|
||||
} __attribute__ ((__packed__)) descriptor;
|
||||
static_assert(sizeof(Descriptor) == 9);
|
||||
|
||||
struct Image {
|
||||
uint8_t lzwMinimumCodeSize;
|
||||
std::vector<std::vector<uint8_t>> imageDataChunks;
|
||||
} image;
|
||||
} frame;
|
||||
};
|
||||
|
||||
static constexpr auto WIDTH = 256;
|
||||
static constexpr auto HEIGHT = 192;
|
||||
|
||||
Gif getGif(std::string_view path) {
|
||||
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");
|
||||
}
|
||||
|
||||
auto& header = gif.header;
|
||||
|
||||
// Read header
|
||||
fread(&header, 1, sizeof(header), file);
|
||||
|
||||
// Check that this is a GIF
|
||||
if (memcmp(header.signature, "GIF87a", sizeof(header.signature)) != 0 && memcmp(header.signature, "GIF89a", sizeof(header.signature)) != 0) {
|
||||
throw std::runtime_error("File not a gif");
|
||||
}
|
||||
|
||||
if(header.width != WIDTH || header.height != HEIGHT) {
|
||||
throw std::runtime_error("Invalid gif size");
|
||||
}
|
||||
|
||||
auto& numColors = gif.numColors;
|
||||
auto& color_table = gif.colorTable;
|
||||
// Load global color table
|
||||
if (header.gctFlag) {
|
||||
numColors = (2 << header.gctSize);
|
||||
fread(color_table.data(), 1, numColors * 3, file);
|
||||
}
|
||||
|
||||
auto& frame = gif.frame;
|
||||
bool gotImage = false;
|
||||
while (1) {
|
||||
switch (fgetc(file)) {
|
||||
case 0x21: { // Extension
|
||||
switch (fgetc(file)) {
|
||||
case 0xF9: { // Graphics Control
|
||||
uint8_t toRead = fgetc(file);
|
||||
fseek(file, toRead, SEEK_CUR);
|
||||
fgetc(file); // Terminator
|
||||
break;
|
||||
}
|
||||
case 0x01: { // Plain text
|
||||
throw std::runtime_error("Plain text found");
|
||||
#if 0
|
||||
fseek(file, 12, SEEK_CUR);
|
||||
while (uint8_t size = fgetc(file)) {
|
||||
fseek(file, size, SEEK_CUR);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
case 0xFF: { // Application extension
|
||||
throw std::runtime_error("Application extension found");
|
||||
#if 0
|
||||
if (fgetc(file) == 0xB) {
|
||||
char buffer[0xC] = {0};
|
||||
fread(buffer, 1, 0xB, file);
|
||||
if (strcmp(buffer, "NETSCAPE2.0") == 0) { // Check for Netscape loop count
|
||||
fseek(file, 2, SEEK_CUR);
|
||||
fseek(file, 2, SEEK_CUR);
|
||||
fgetc(file); //terminator
|
||||
break;
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
#endif
|
||||
}
|
||||
case 0xFE: { // Comment
|
||||
// Skip comments and unsupported application extionsions
|
||||
while (uint8_t size = fgetc(file)) {
|
||||
fseek(file, size, SEEK_CUR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw std::runtime_error("Unknown GIF extension found");
|
||||
}
|
||||
}
|
||||
break;
|
||||
} case 0x2C: { // Image descriptor
|
||||
gotImage = true;
|
||||
fread(&frame.descriptor, 1, sizeof(frame.descriptor), file);
|
||||
if (frame.descriptor.lctFlag) {
|
||||
header.gctFlag = 1;
|
||||
header.gctSize = frame.descriptor.lctSize;
|
||||
header.sortFlag = frame.descriptor.sortFlag;
|
||||
numColors = 2 << header.gctSize;
|
||||
fread(color_table.data(), 1, numColors * 3, file);
|
||||
frame.descriptor.lctFlag = 0;
|
||||
frame.descriptor.lctSize = 0;
|
||||
frame.descriptor.sortFlag = 0;
|
||||
}
|
||||
|
||||
if(frame.descriptor.w != WIDTH || frame.descriptor.h != HEIGHT) {
|
||||
throw std::runtime_error("Wrong frame size");
|
||||
}
|
||||
|
||||
if(frame.descriptor.x != 0 || frame.descriptor.y != 0) {
|
||||
throw std::runtime_error("Wrong frame coordinates");
|
||||
}
|
||||
|
||||
frame.image.lzwMinimumCodeSize = fgetc(file);
|
||||
while (uint8_t size = fgetc(file)) {
|
||||
std::vector<uint8_t> dataChunk;
|
||||
dataChunk.resize(size);
|
||||
fread(dataChunk.data(), 1, size, file);
|
||||
frame.image.imageDataChunks.push_back(std::move(dataChunk));
|
||||
}
|
||||
|
||||
goto breakWhile;
|
||||
} case 0x3B: { // Trailer
|
||||
goto breakWhile;
|
||||
}
|
||||
}
|
||||
if(feof(file)){
|
||||
throw std::runtime_error("Unexpected file termination");
|
||||
}
|
||||
}
|
||||
breakWhile:
|
||||
if(!gotImage){
|
||||
throw std::runtime_error("Image data not found in gif");
|
||||
}
|
||||
if(!header.gctFlag) {
|
||||
throw std::runtime_error("Invalid gif (missing color table)");
|
||||
}
|
||||
return gif;
|
||||
}
|
||||
|
||||
std::span<uint8_t> writeGif(const Gif& gif, std::array<uint8_t, MAX_GIF_SIZE>& outArr) {
|
||||
size_t totalWritten = 0;
|
||||
auto writeArr = [&, ptr = outArr.data()](const void* data, size_t len) mutable {
|
||||
if((totalWritten + len) > MAX_GIF_SIZE)
|
||||
throw std::runtime_error("Gif too big");
|
||||
memcpy(ptr, data, len);
|
||||
ptr += len;
|
||||
totalWritten += len;
|
||||
};
|
||||
auto writeCh = [&](char ch) mutable {
|
||||
writeArr(&ch, 1);
|
||||
};
|
||||
|
||||
writeArr(&gif.header, sizeof(gif.header));
|
||||
writeArr(gif.colorTable.data(), gif.numColors * 3);
|
||||
|
||||
// write single image descriptor
|
||||
writeCh(0x2C);
|
||||
writeArr(&gif.frame.descriptor, sizeof(gif.frame.descriptor));
|
||||
|
||||
// write image data
|
||||
writeCh(gif.frame.image.lzwMinimumCodeSize);
|
||||
for(const auto& chunk : gif.frame.image.imageDataChunks){
|
||||
writeCh(chunk.size());
|
||||
writeArr(chunk.data(), chunk.size());
|
||||
}
|
||||
writeCh('\0');
|
||||
|
||||
// write trailer
|
||||
writeCh(0x3B);
|
||||
return std::span{outArr.data(), totalWritten};
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
12
arm9/src/gifConverter.h
Normal file
12
arm9/src/gifConverter.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef GIF_CONVERTER_H
|
||||
#define GIF_CONVERTER_H
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
#include "unlaunch.h"
|
||||
|
||||
std::span<uint8_t> parseGif(const char* path, std::array<uint8_t, MAX_GIF_SIZE>& outArr);
|
||||
|
||||
#endif //GIF_CONVERTER_H
|
||||
@ -26,7 +26,7 @@ static UNLAUNCH_VERSION foundUnlaunchInstallerVersion = INVALID;
|
||||
static bool disableAllPatches = false;
|
||||
static bool enableSoundAndSplash = false;
|
||||
static const char* splashSoundBinaryPatchPath = NULL;
|
||||
static const char* customBgPath = NULL;
|
||||
static std::span<uint8_t> customBgSpan{};
|
||||
volatile bool charging = false;
|
||||
volatile u8 batteryLevel = 0;
|
||||
static bool advancedOptionsUnlocked = false;
|
||||
@ -590,7 +590,7 @@ void install(consoleInfo& info) {
|
||||
}
|
||||
if(installUnlaunch(info, disableAllPatches,
|
||||
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL,
|
||||
customBgPath))
|
||||
customBgSpan))
|
||||
{
|
||||
messageBox("Install successful!\n");
|
||||
info.tmdGood = false;
|
||||
@ -615,19 +615,7 @@ void customBg() {
|
||||
{
|
||||
return;
|
||||
}
|
||||
const char* customBg = backgroundMenu();
|
||||
if(!customBg)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(strcmp(customBg, "default") == 0)
|
||||
{
|
||||
customBgPath = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
customBgPath = customBg;
|
||||
}
|
||||
customBgSpan = backgroundMenu();
|
||||
}
|
||||
|
||||
void doMainMenu(consoleInfo& info) {
|
||||
|
||||
@ -3,10 +3,12 @@
|
||||
#include "storage.h"
|
||||
#include "tonccpy.h"
|
||||
#include "unlaunch.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include <nds/sha1.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <memory>
|
||||
#include <format>
|
||||
@ -425,38 +427,19 @@ static bool verifyUnlaunchInstaller(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool patchCustomBackground(const char* customBackgroundPath)
|
||||
static bool patchCustomBackground(std::span<uint8_t> customBackground)
|
||||
{
|
||||
if(!customBackgroundPath)
|
||||
auto size = customBackground.size();
|
||||
if(size == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto bgGif = fopen(customBackgroundPath, "rb");
|
||||
if(!bgGif)
|
||||
if(size < 7 || size > MAX_GIF_SIZE)
|
||||
{
|
||||
messageBox("\x1B[31mError:\x1B[33m Failed to open custom bg gif.\n");
|
||||
return false;
|
||||
}
|
||||
auto size = getFileSize(bgGif);
|
||||
if(size < 7 || size > 0x3C70)
|
||||
{
|
||||
messageBox("\x1B[31mError:\x1B[33m Invalid gif file.\n");
|
||||
fclose(bgGif);
|
||||
return false;
|
||||
}
|
||||
u16 gifWidth;
|
||||
u16 gifHeight;
|
||||
if((fseek(bgGif, 6, SEEK_SET) != 0) || (fread(&gifWidth, 1, sizeof(u16), bgGif) != sizeof(u16)) || (fread(&gifHeight, 1, sizeof(u16), bgGif) != sizeof(u16)))
|
||||
{
|
||||
messageBox("\x1B[31mError:\x1B[33m Failed to parse gif file.\n");
|
||||
fclose(bgGif);
|
||||
return false;
|
||||
}
|
||||
if (gifWidth != 256 || gifHeight != 192) {
|
||||
messageBox("\x1B[31mError:\x1B[33m Gif file has invalid dimensions.\n");
|
||||
fclose(bgGif);
|
||||
messageBox("\x1B[31mError:\x1B[33m Gif file too big.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 gifSignatureStart = 0x38464947;
|
||||
const u32 gifSignatureEnd = 0x3B000044;
|
||||
|
||||
@ -468,14 +451,11 @@ static bool patchCustomBackground(const char* customBackgroundPath)
|
||||
if(*gifStart != gifSignatureStart || *gifEnd != gifSignatureEnd)
|
||||
{
|
||||
messageBox("\x1B[31mError:\x1B[33m Gif offsets not matching.\n");
|
||||
fclose(bgGif);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(bgGif, 0, SEEK_SET);
|
||||
|
||||
//read the whole file, could be less than 0x3C70, but unlaunch should then just ignore the leftover data
|
||||
fread(gifStart, 1, 0x3C70, bgGif);
|
||||
std::memcpy(gifStart, customBackground.data(), size);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -507,7 +487,7 @@ static bool applyBinaryPatch(const char* path)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath)
|
||||
static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSoundBinaryPatchPath, std::span<uint8_t> customBackground)
|
||||
{
|
||||
tonccpy(unlaunchInstallerBuffer, ogUnlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer));
|
||||
if (splashSoundBinaryPatchPath)
|
||||
@ -530,7 +510,7 @@ static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSou
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(!patchCustomBackground(customBackgroundPath))
|
||||
if(!patchCustomBackground(customBackground))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -559,9 +539,9 @@ const char* getUnlaunchVersionString(UNLAUNCH_VERSION version)
|
||||
return unlaunchVersionStrings[version];
|
||||
}
|
||||
|
||||
bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath)
|
||||
bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, std::span<uint8_t> customBackground)
|
||||
{
|
||||
if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath, customBackgroundPath))
|
||||
if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath, customBackground))
|
||||
return false;
|
||||
|
||||
// Treat protos differently
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#ifndef UNLAUNCH_H
|
||||
#define UNLAUNCH_H
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
|
||||
#include "consoleInfo.h"
|
||||
|
||||
@ -9,10 +10,12 @@ typedef enum UNLAUNCH_VERSION {
|
||||
INVALID,
|
||||
} UNLAUNCH_VERSION;
|
||||
|
||||
static constexpr auto MAX_GIF_SIZE = 0x3C70;
|
||||
|
||||
const char* getUnlaunchVersionString(UNLAUNCH_VERSION);
|
||||
|
||||
bool uninstallUnlaunch(const consoleInfo& info, bool removeHNAABackup);
|
||||
bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath);
|
||||
bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, std::span<uint8_t> customBackground);
|
||||
|
||||
UNLAUNCH_VERSION loadUnlaunchInstaller(std::string_view path);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user