Initial commit

This commit is contained in:
Cavv 2025-03-13 03:12:51 +01:00
commit efe6cc1b29
81 changed files with 6701 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build/
Kekatsu.elf
Kekatsu.nds
release/
Kekatsu-DS.zip
version.txt
.vscode/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Cavv
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.

93
Makefile Normal file
View File

@ -0,0 +1,93 @@
.SUFFIXES:
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/ds_rules
TARGET := Kekatsu
BUILD := build
RELEASE := release
SOURCES := source source/utils source/gui
DATA := data
INCLUDES := include
GRAPHICS := source/gfx source/gui/gfx
LANGUAGES := source/lang
VERSION := 1.0.0
ARCH := -march=armv5te -mtune=arm946e-s -mthumb
CFLAGS := -g -Wall -O2 -ffunction-sections -fdata-sections $(ARCH) $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS := -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lminizip -lcurl -lmbedtls -lmbedcrypto -lmbedx509 -lpng -lm -lz -ldswifi9 -lfat -lnds9
LIBDIRS := $(PORTLIBS) $(LIBNDS)
ifneq ($(BUILDDIR), $(CURDIR))
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \
$(foreach dir,$(LANGUAGES),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
LANGFILES := $(foreach dir,$(LANGUAGES),$(notdir $(wildcard $(dir)/*.lang*)))
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
export OFILES_LANG := $(addsuffix .o,$(LANGFILES))
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(PNGFILES:.png=.o) $(OFILES_LANG) $(OFILES_SOURCES)
export HFILES := $(PNGFILES:.png=.h) $(addsuffix .h,$(subst .,_,$(LANGFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export GAME_TITLE := Kekatsu
export GAME_SUBTITLE1 := DS(i) content downloader
export GAME_SUBTITLE2 := Cavv
export GAME_ICON := $(CURDIR)/icon.bmp
.PHONY: $(BUILD) clean
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make BUILDDIR=`cd $(BUILD) && pwd` --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
$(RELEASE): $(BUILD)
@[ -d $@ ] || mkdir -p $@
@mv $(TARGET).nds $(RELEASE)/$(TARGET).nds
@echo $(VERSION) > $(RELEASE)/version.txt
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(RELEASE)
else
DEPENDS := $(OFILES:.o=.d)
$(OUTPUT).nds: $(OUTPUT).elf
$(OUTPUT).elf: $(OFILES)
%.s %.h: %.png %.grit
grit $< -fts -o$*
%.lang.o %_lang.h : %.lang
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
endif

93
README.md Normal file
View File

@ -0,0 +1,93 @@
# Kekatsu DS
Easy-to-use content downloader for Nintendo DS(i) consoles
![Screenshot 1](https://github.com/cavv-dev/Kekatsu-DS/raw/main/resources/screenshots/Kekatsu-DS_1.png) ![Screenshot 2](https://github.com/cavv-dev/Kekatsu-DS/raw/main/resources/screenshots/Kekatsu-DS_2.png) ![Screenshot 3](https://github.com/cavv-dev/Kekatsu-DS/raw/main/resources/screenshots/Kekatsu-DS_3.png)
The main scope of this project is to have a standalone and easy way to download apps and games on the fly, given a database by the user.
Concept inspired by [pkgi-psp](https://github.com/bucanero/pkgi-psp) and [Universal-Updater](https://github.com/Universal-Team/Universal-Updater).
## Features
- Multi-platform content download
- Database loading via URL or file
- Support for multiple databases
- ZIP file extraction
- Automatic updates check
- Customizable color scheme
- Localization support
## Databases setup
Kekatsu expects a `databases.txt` file to load as the list of available databases. This file has to be located in the `Kekatsu` directory on the root of the SD card.
Each line of the `databases.txt` file has to contain a name and a value separated by a tab character.
The name should be the display name of the database. The value must be either:
- an HTTP(S) URL which returns a database in text response
- a path to a database file on the SD card itself
### Example databases.txt
```
test-db https://example.com/database.txt
test-db2 /databases/db2.txt
```
## Community databases
Collection of useful known databases. Contact me to if you want to contribute to this list.
| Name | Description | URL |
|---|---|---|
| [UDB-Kekatsu-DS](https://github.com/cavv-dev/UDB-Kekatsu-DS) | Updated selection of DS and DSi apps from Universal-DB | `https://gist.githubusercontent.com/cavv-dev/3c0cbc1b63ac8ca0c1d9f549403afbf1/raw/` |
## Database creation instructions
A database file expected by Kekatsu is a text file that follows this precise structure:
- **Line 1**: Database version - The database version to be used by the parser. Follow the next instructions that match the chosen database version.
<details><summary>Version 1</summary>
- **Line 2**: Delimiter character - The character to be used to separate fields in the next lines
- **Line 3 and above**: Fields separated by the delimiter character. They must follow this order:
- **Title** - Display title of the content
- **Platform** - Target platform of the content. *Should* be in lowercase and in its abbreviated form as it will be used as the name of the platform directory. E.g. `nds` instead of `Nintendo DS`.
- **Region** - Target region of the content. Could be `NTSC-U`, `PAL` and similar for contents which target a specific region or `ANY` for contents made for any region.
- **Version** - Release version of the content
- **Author** - Author or publisher of the content
- **Download URL** - The HTTP(S) URL to download the content. Must be a direct link to the file of the content. This file can be an executable or an archive in ZIP format.
- **File name** - The name under which the downloaded file will be saved
- **Size** - The size in bytes of the downloaded file
- **Box art URL** - The HTTP(S) URL of the displayed box art for the content. A box art is expected to be in PNG format.
- **Extract items** - The items to be extracted from the downloaded archive in couples of fields separated by the delimiter character. Each couple is composed of:
- **In-path** - The path of the file or directory in the archive to be extracted. Directories should have `/` as the last character.
- **Out-path** - The destination path of the extracted file or directory
If no extract items are specified, all the files and directories will be extracted following the structure in the archive.
They are not going to be checked if the downloaded file is not an archive.
### Example database file
```
1
,
test-app,nds,ANY,1.0,Author1,https://example.com/test-app-v1.0.nds,test-app.nds,1048576,https://example.com/test-app-boxart.png
test-app2,gba,NTSC-U,1.1,Author2,https://example.com/test-app2.zip,test-app2.zip,2097152,https://example.com/test-app2-boxart.png,release/gba/test-app2-v1.1.gba,test-app2.gba
```
</details>
## Building
### Requirements
- devkitARM toolchain (`nds-dev` package group) by [devkitPro](https://github.com/devkitPro)
- [curl](https://github.com/ds-sloth/pacman-packages/tree/nds-curl-mbedtls/nds/curl) and [mbedtls](https://github.com/ds-sloth/pacman-packages/tree/nds-curl-mbedtls/nds/mbedtls) by [ds-sloth](https://github.com/ds-sloth)
- nds-zlib, nds-libpng from devkitPro pacman repository
```sh
git clone https://github.com/cavv-dev/Kekatsu-DS.git
cd Kekatsu-DS
make release
```
## Credits
- [Cavv](https://github.com/cavv-dev): Main developer
- [devkitPro](https://github.com/devkitPro): devkitARM and relative libraries
- [ds-sloth](https://github.com/ds-sloth): curl and mbedtls libraries
- [Flaticon](https://www.flaticon.com/): Icons

BIN
icon.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

158
source/archives.c Normal file
View File

@ -0,0 +1,158 @@
#include "archives.h"
#include "utils/filesystem.h"
#include "utils/strings.h"
#include <calico/types.h>
#include <minizip/unzip.h>
#include <string.h>
bool fileIsZip(const char* filePath)
{
FILE* fp = fopen(filePath, "rb");
if (!fp)
return false;
unsigned char buffer[4];
size_t bytesRead = fread(buffer, 1, 4, fp);
fclose(fp);
return (bytesRead == 4 && buffer[0] == 0x50 && buffer[1] == 0x4b && buffer[2] == 0x03 && buffer[3] == 0x04);
}
ExtractStatus extractFile(unzFile zipFile, const char* outFilePath)
{
char zipFilePath[PATH_MAX];
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK)
return EXTRACT_ERR_FILE_READ;
createDirStructure(outFilePath);
if (zipFilePath[strlen(zipFilePath) - 1] == '/') {
if (createDir(outFilePath))
return EXTRACT_SUCCESS;
else
return EXTRACT_ERR_FILE_WRITE;
}
FILE* fp = fopen(outFilePath, "wb");
if (!fp)
return EXTRACT_ERR_FILE_WRITE;
char buffer[128];
int bytesRead;
while ((bytesRead = unzReadCurrentFile(zipFile, buffer, sizeof(buffer))) > 0) {
if (fwrite(buffer, bytesRead, 1, fp) != 1) {
fclose(fp);
return EXTRACT_ERR_FILE_WRITE;
}
}
fclose(fp);
return (bytesRead < 0) ? EXTRACT_ERR_FILE_READ : EXTRACT_SUCCESS;
}
ExtractStatus extractZip(const char* filePath, const char* inPath, const char* outPath)
{
unzFile zipFile = unzOpen(filePath);
if (!zipFile)
return EXTRACT_ERR_FILE_OPEN;
if (inPath[strlen(inPath) - 1] == '/') {
if (unzGoToFirstFile(zipFile) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
do {
char zipFilePath[PATH_MAX];
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
if (strncmp(zipFilePath, inPath, strlen(inPath)) != 0)
continue;
char* outDirName = zipFilePath + strlen(inPath);
if (strlen(outPath) + strlen(outDirName) > PATH_MAX - 1)
return EXTRACT_ERR_FILE_READ;
char newOutPath[PATH_MAX];
joinPath(newOutPath, outPath, outDirName);
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
ExtractStatus result = extractFile(zipFile, newOutPath);
unzCloseCurrentFile(zipFile);
if (result != EXTRACT_SUCCESS) {
unzClose(zipFile);
return result;
}
} while (unzGoToNextFile(zipFile) == UNZ_OK);
} else {
if (unzLocateFile(zipFile, inPath, 0) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_NOT_FOUND;
}
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
ExtractStatus result = extractFile(zipFile, outPath);
unzCloseCurrentFile(zipFile);
if (result != EXTRACT_SUCCESS) {
unzClose(zipFile);
return result;
}
}
unzClose(zipFile);
return EXTRACT_SUCCESS;
}
ExtractStatus extractAllZip(const char* filePath, const char* outDir)
{
unzFile zipFile = unzOpen(filePath);
if (!zipFile)
return EXTRACT_ERR_FILE_OPEN;
if (unzGoToFirstFile(zipFile) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
do {
if (unzOpenCurrentFile(zipFile) != UNZ_OK) {
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
char zipFilePath[PATH_MAX];
if (unzGetCurrentFileInfo(zipFile, NULL, zipFilePath, sizeof(zipFilePath), NULL, 0, NULL, 0) != UNZ_OK) {
unzCloseCurrentFile(zipFile);
unzClose(zipFile);
return EXTRACT_ERR_FILE_READ;
}
if (strlen(outDir) + strlen(zipFilePath) > PATH_MAX - 1)
return EXTRACT_ERR_FILE_READ;
char outFilePath[PATH_MAX];
joinPath(outFilePath, outDir, zipFilePath);
ExtractStatus result = extractFile(zipFile, outFilePath);
unzCloseCurrentFile(zipFile);
if (result != EXTRACT_SUCCESS) {
unzClose(zipFile);
return result;
}
} while (unzGoToNextFile(zipFile) == UNZ_OK);
unzClose(zipFile);
return EXTRACT_SUCCESS;
}

14
source/archives.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
typedef enum {
EXTRACT_SUCCESS,
EXTRACT_ERR_FILE_OPEN,
EXTRACT_ERR_FILE_NOT_FOUND,
EXTRACT_ERR_FILE_READ,
EXTRACT_ERR_FILE_WRITE
} ExtractStatus;
bool fileIsZip(const char* filePath);
ExtractStatus extractZip(const char* filePath, const char* inFilePath, const char* outFilePath);
ExtractStatus extractAllZip(const char* filePath, const char* outDir);

54
source/colors.c Normal file
View File

@ -0,0 +1,54 @@
#include "colors.h"
u16 colorSchemes[COLOR_SCHEMES_COUNT][COLORS_COUNT];
void initColorSchemes(void)
{
colorSchemes[COLOR_SCHEME_1][COLOR_BG] = RGB15_8BIT(9, 9, 15);
colorSchemes[COLOR_SCHEME_1][COLOR_BG_2] = RGB15_8BIT(20, 20, 33);
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT] = RGB15_8BIT(168, 168, 237);
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_1][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_1][COLOR_PRIMARY] = RGB15_8BIT(110, 110, 186);
colorSchemes[COLOR_SCHEME_1][COLOR_SECONDARY] = RGB15_8BIT(55, 55, 105);
colorSchemes[COLOR_SCHEME_2][COLOR_BG] = RGB15_8BIT(9, 12, 15);
colorSchemes[COLOR_SCHEME_2][COLOR_BG_2] = RGB15_8BIT(20, 26, 33);
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT] = RGB15_8BIT(168, 197, 237);
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_2][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_2][COLOR_PRIMARY] = RGB15_8BIT(110, 142, 186);
colorSchemes[COLOR_SCHEME_2][COLOR_SECONDARY] = RGB15_8BIT(54, 75, 105);
colorSchemes[COLOR_SCHEME_3][COLOR_BG] = RGB15_8BIT(15, 14, 9);
colorSchemes[COLOR_SCHEME_3][COLOR_BG_2] = RGB15_8BIT(33, 30, 20);
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT] = RGB15_8BIT(237, 219, 168);
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_3][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_3][COLOR_PRIMARY] = RGB15_8BIT(186, 166, 110);
colorSchemes[COLOR_SCHEME_3][COLOR_SECONDARY] = RGB15_8BIT(105, 91, 54);
colorSchemes[COLOR_SCHEME_4][COLOR_BG] = RGB15_8BIT(15, 9, 9);
colorSchemes[COLOR_SCHEME_4][COLOR_BG_2] = RGB15_8BIT(33, 20, 20);
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT] = RGB15_8BIT(237, 168, 170);
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_4][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_4][COLOR_PRIMARY] = RGB15_8BIT(186, 110, 111);
colorSchemes[COLOR_SCHEME_4][COLOR_SECONDARY] = RGB15_8BIT(105, 54, 55);
colorSchemes[COLOR_SCHEME_5][COLOR_BG] = RGB15_8BIT(9, 15, 12);
colorSchemes[COLOR_SCHEME_5][COLOR_BG_2] = RGB15_8BIT(20, 33, 25);
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT] = RGB15_8BIT(168, 237, 196);
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_5][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_5][COLOR_PRIMARY] = RGB15_8BIT(110, 186, 140);
colorSchemes[COLOR_SCHEME_5][COLOR_SECONDARY] = RGB15_8BIT(54, 105, 74);
colorSchemes[COLOR_SCHEME_6][COLOR_BG] = RGB15_8BIT(15, 15, 15);
colorSchemes[COLOR_SCHEME_6][COLOR_BG_2] = RGB15_8BIT(33, 33, 33);
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT] = RGB15_8BIT(237, 237, 237);
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT_2] = RGB15_8BIT(243, 243, 243);
colorSchemes[COLOR_SCHEME_6][COLOR_TEXT_3] = RGB15_8BIT(194, 194, 194);
colorSchemes[COLOR_SCHEME_6][COLOR_PRIMARY] = RGB15_8BIT(186, 186, 186);
colorSchemes[COLOR_SCHEME_6][COLOR_SECONDARY] = RGB15_8BIT(105, 105, 105);
}

29
source/colors.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <calico/types.h>
typedef enum {
COLOR_SCHEME_1,
COLOR_SCHEME_2,
COLOR_SCHEME_3,
COLOR_SCHEME_4,
COLOR_SCHEME_5,
COLOR_SCHEME_6,
COLOR_SCHEMES_COUNT
} ColorSchemeEnum;
typedef enum {
COLOR_BG,
COLOR_BG_2,
COLOR_TEXT,
COLOR_TEXT_2,
COLOR_TEXT_3,
COLOR_PRIMARY,
COLOR_SECONDARY,
COLORS_COUNT
} ColorEnum;
#define RGB15_8BIT(r, g, b) (((r) >> 3) | (((g) >> 3) << 5) | (((b) >> 3) << 10))
extern u16 colorSchemes[COLOR_SCHEMES_COUNT][COLORS_COUNT];
void initColorSchemes(void);

9
source/config.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
#define APP_NAME "Kekatsu"
#define APP_VERSION "1.0.0"
#define APPDATA_DIR "/" APP_NAME
#define CACHE_DIR APPDATA_DIR "/cache"
#define UPDATE_URL_APP "https://github.com/cavv-dev/Kekatsu-DS/releases/latest/download/Kekatsu.nds"
#define UPDATE_URL_VERSION "https://github.com/cavv-dev/Kekatsu-DS/releases/latest/download/version.txt"

376
source/database.c Normal file
View File

@ -0,0 +1,376 @@
#include "database.h"
#include "config.h"
#include "networking.h"
#include "utils/filesystem.h"
#include "utils/strings.h"
#include <stdlib.h>
#include <string.h>
#define TEMP_DB_FILENAME "db.txt"
#define TEMP_DB_PATH CACHE_DIR "/" TEMP_DB_FILENAME
#define LAST_OPENED_DB_FILENAME "lastOpenedDb.txt"
#define LAST_OPENED_DB_PATH APPDATA_DIR "/" LAST_OPENED_DB_FILENAME
#define DATABASE_LIST_FILENAME "databases.txt"
#define DATABASE_LIST_PATH APPDATA_DIR "/" DATABASE_LIST_FILENAME
struct Database {
char* name;
char* value;
DatabaseType type;
char* path;
size_t size;
bool isInited;
FILE* fp;
char delimiter;
};
Database newDatabase(const char* name, const char* value)
{
Database d = malloc(sizeof(struct Database));
d->name = strdup(name);
d->value = strdup(value);
d->path = NULL;
d->size = 0;
d->isInited = false;
d->fp = NULL;
d->delimiter = '\0';
if ((strncmp(value, "http://", 7) == 0) || (strncmp(value, "https://", 8) == 0))
d->type = DATABASE_TYPE_HTTP;
else
d->type = DATABASE_TYPE_LOCAL;
return d;
}
void freeDatabase(Database d)
{
free(d->value);
free(d->path);
if (d->fp)
closeDatabase(d);
free(d);
}
char* getDatabaseName(Database d)
{
return d->name;
}
char* getDatabaseValue(Database d)
{
return d->value;
}
char* getDatabasePath(Database d)
{
return d->path;
}
size_t getDatabaseSize(Database d)
{
return d->size;
}
bool getDatabaseIsInited(Database d)
{
return d->isInited;
}
char** parseLine(char* line, char delimiter, size_t* fieldsCount)
{
if (line[0] == '\0') {
*fieldsCount = 0;
return NULL;
}
// Count fields in line
*fieldsCount = 1;
for (char* ptr = line; *ptr != '\0'; ++ptr) {
if (*ptr == delimiter)
(*fieldsCount)++;
}
// Allocate space for fields array
char** fields = (char**)malloc(*fieldsCount * sizeof(char*));
if (fields == NULL)
return NULL;
// Populate fields array with pointers to strings in line
size_t fieldIndex = 0;
char* start = line;
for (char* ptr = line;; ++ptr) {
if (*ptr == delimiter || *ptr == '\0') {
fields[fieldIndex] = start;
fieldIndex++;
if (*ptr == '\0') {
break;
}
*ptr = '\0'; // Replace delimiter with null terminator
start = ptr + 1;
}
}
return fields;
}
Database getLastOpenedDatabase(void)
{
FILE* fp = fopen(LAST_OPENED_DB_PATH, "r");
if (!fp)
return NULL;
char line[2048];
char name[1024];
char value[1024];
if (!fgets(line, sizeof(line), fp) || sscanf(line, "%[^\t]\t%s", name, value) != 2) {
fclose(fp);
return NULL;
}
fclose(fp);
return newDatabase(name, value);
}
void saveLastOpenedDatabase(Database d)
{
FILE* fp = fopen(LAST_OPENED_DB_PATH, "w");
if (!fp)
return;
fprintf(fp, "%s\t%s\n", d->name, d->value);
fclose(fp);
}
DatabaseInitStatus openDatabase(Database d)
{
FILE* fp = fopen(d->path, "r");
if (!fp)
return DATABASE_OPEN_ERR_FILE_OPEN;
size_t dbSize = 0;
char line[1024];
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
int dbVersion = atoi(line);
if (dbVersion != 1) { // 1 is the only supported version for now
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_VERSION;
}
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
char delimiter = line[0];
while (fgets(line, sizeof(line), fp)) {
size_t fieldsCount;
char** fields = parseLine(line, delimiter, &fieldsCount);
if (!fields || fieldsCount < 9) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
// Calculate the fields count of items to extract
// Items are couple of fields so the count has to be an even number
size_t extractItemsFieldsCount = fieldsCount - 9;
if (fieldsCount > 9 && (extractItemsFieldsCount & 1) != 0) {
free(fields);
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
dbSize++;
}
d->size = dbSize;
d->fp = fp;
d->delimiter = delimiter;
saveLastOpenedDatabase(d);
return DATABASE_OPEN_SUCCESS;
}
bool closeDatabase(Database d)
{
if (fclose(d->fp) != 0)
return false;
return true;
}
DatabaseInitStatus initDatabase(Database d)
{
bool usingCachedDb = false;
switch (d->type) {
case DATABASE_TYPE_HTTP:
char* dbFileName = strdup(d->value);
safeStr(dbFileName);
char dbFilePath[PATH_MAX];
snprintf(dbFilePath, sizeof(dbFilePath), "%s/%s", CACHE_DIR, dbFileName);
free(dbFileName);
d->path = strdup(dbFilePath);
if (downloadFile(TEMP_DB_PATH, d->value, NULL) == DOWNLOAD_SUCCESS) {
if (!renamePath(TEMP_DB_PATH, dbFilePath))
return DATABASE_INIT_ERR_DOWNLOAD;
} else {
if (fileExists(dbFilePath))
usingCachedDb = true;
else
return DATABASE_INIT_ERR_DOWNLOAD;
}
break;
case DATABASE_TYPE_LOCAL:
d->path = strdup(d->value);
if (!pathExists(d->value))
return DATABASE_INIT_ERR_FILE_NOT_FOUND;
break;
}
d->isInited = true;
return (usingCachedDb ? DATABASE_INIT_SUCCESS_CACHE : DATABASE_INIT_SUCCESS);
}
void alignDatabase(Database d)
{
fseek(d->fp, 0, SEEK_SET);
// Advance 2 lines
char ch;
u8 lines = 2;
while (lines > 0 && (ch = fgetc(d->fp)) != EOF) {
if (ch == '\n') {
lines--;
}
}
}
Entry* searchDatabase(Database d, const char* searchTitle, size_t pageSize, size_t page, size_t* resultsCount)
{
*resultsCount = 0;
size_t count = 0;
size_t capacity = 10;
size_t startIndex = (page - 1) * pageSize;
size_t endIndex = startIndex + pageSize;
size_t currIndex = 0;
char line[1024];
Entry* results = malloc(capacity * sizeof(Entry));
alignDatabase(d);
while (fgets(line, sizeof(line), d->fp)) {
// Trim trailing newline
line[strcspn(line, "\r\n")] = '\0';
size_t fieldsCount;
char** fields = parseLine(line, d->delimiter, &fieldsCount);
if (searchTitle[0] != '\0') {
// Format titles in a comparable way
char tempEntryTitle[128];
char tempInputTitle[128];
strncpy(tempEntryTitle, fields[0], sizeof(tempEntryTitle) - 1);
removeAccentsStr(tempEntryTitle);
lowerStr(tempEntryTitle);
strncpy(tempInputTitle, searchTitle, sizeof(tempInputTitle) - 1);
lowerStr(tempInputTitle);
if (!strstr(tempEntryTitle, tempInputTitle)) {
free(fields);
continue;
}
}
if (currIndex < startIndex || currIndex >= endIndex) {
currIndex++;
continue;
}
char** extractItemsFields = &fields[9]; // Items to extract start from the ninth field
size_t extractItemsCount = (fieldsCount - 9) / 2;
struct EntryExtractItem extractItems[extractItemsCount];
for (size_t i = 0; i < extractItemsCount; i++) {
extractItems[i].outPath = extractItemsFields[i * 2];
extractItems[i].inPath = extractItemsFields[i * 2 + 1];
}
Entry e = newEntry(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], strtoull(fields[7], NULL, 10), fields[8], extractItems, extractItemsCount);
// Increase the capacity if needed
if (count >= capacity) {
capacity *= 2;
results = realloc(results, capacity * sizeof(Entry));
}
results[count] = e;
count++;
currIndex++;
free(fields);
if (currIndex >= endIndex)
break;
}
*resultsCount = count;
return results;
}
Database* getDatabaseList(size_t* databasesCount)
{
*databasesCount = 0;
FILE* fp = fopen(DATABASE_LIST_PATH, "r");
if (!fp)
return NULL;
size_t count = 0;
size_t capacity = 8;
Database* databases = malloc(capacity * sizeof(Database));
char line[2048];
char name[1024];
char value[1024];
while (fgets(line, sizeof(line), fp)) {
if (sscanf(line, "%[^\t]\t%s", name, value) != 2)
continue;
if (count >= capacity) {
capacity *= 2;
databases = realloc(databases, capacity * sizeof(Database));
}
databases[count++] = newDatabase(name, value);
}
fclose(fp);
*databasesCount = count;
return databases;
}

39
source/database.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include "entries.h"
#include <stdbool.h>
#include <stddef.h>
typedef struct Database* Database;
typedef enum {
DATABASE_TYPE_HTTP,
DATABASE_TYPE_LOCAL
} DatabaseType;
typedef enum {
DATABASE_INIT_SUCCESS,
DATABASE_INIT_SUCCESS_CACHE,
DATABASE_INIT_ERR_DOWNLOAD,
DATABASE_INIT_ERR_FILE_NOT_FOUND
} DatabaseInitStatus;
typedef enum {
DATABASE_OPEN_SUCCESS,
DATABASE_OPEN_ERR_FILE_OPEN,
DATABASE_OPEN_ERR_INVALID_VERSION,
DATABASE_OPEN_ERR_INVALID_FORMAT
} DatabaseOpenStatus;
Database newDatabase(const char* name, const char* value);
void freeDatabase(Database);
char* getDatabaseName(Database);
char* getDatabaseValue(Database);
char* getDatabasePath(Database);
size_t getDatabaseSize(Database);
bool getDatabaseIsInited(Database);
Database getLastOpenedDatabase(void);
DatabaseInitStatus openDatabase(Database);
bool closeDatabase(Database d);
DatabaseInitStatus initDatabase(Database);
Entry* searchDatabase(Database, const char* searchTitle, size_t pageSize, size_t page, size_t* resultsCount);
Database* getDatabaseList(size_t* databasesCount);

120
source/entries.c Normal file
View File

@ -0,0 +1,120 @@
#include "entries.h"
#include <stdlib.h>
#include <string.h>
struct Entry {
char* title;
char* platform;
char* region;
char* version;
char* author;
char* url;
char* fileName;
u64 size;
char* boxartUrl;
struct EntryExtractItem* extractItems;
size_t extractItemsCount;
};
Entry newEntry(const char* title, const char* platform, char* region, const char* version, const char* author, const char* url, const char* fileName, u64 size, const char* boxartUrl, struct EntryExtractItem* extractItems, size_t extractItemsCount)
{
Entry e = malloc(sizeof(struct Entry));
e->title = strdup(title);
e->platform = strdup(platform);
e->region = strdup(region);
e->version = strdup(version);
e->author = strdup(author);
e->url = strdup(url);
e->fileName = strdup(fileName);
e->size = size;
e->boxartUrl = strdup(boxartUrl);
e->extractItems = malloc(sizeof(struct EntryExtractItem) * extractItemsCount);
for (int i = 0; i < extractItemsCount; i++) {
e->extractItems[i].outPath = strdup(extractItems[i].outPath);
e->extractItems[i].inPath = strdup(extractItems[i].outPath);
}
e->extractItemsCount = extractItemsCount;
return e;
}
void freeEntry(Entry e)
{
free(e->title);
free(e->platform);
free(e->version);
free(e->author);
free(e->url);
free(e->boxartUrl);
for (int i = 0; i < e->extractItemsCount; i++) {
free(e->extractItems[i].outPath);
free(e->extractItems[i].inPath);
}
free(e->extractItems);
free(e);
}
char* getEntryTitle(Entry e)
{
return e->title;
}
char* getEntryPlatform(Entry e)
{
return e->platform;
}
char* getEntryRegion(Entry e)
{
return e->region;
}
char* getEntryVersion(Entry e)
{
return e->version;
}
char* getEntryAuthor(Entry e)
{
return e->author;
}
char* getEntryUrl(Entry e)
{
return e->url;
}
char* getEntryFileName(Entry e)
{
return e->fileName;
}
u64 getEntrySize(Entry e)
{
return e->size;
}
char* getEntryBoxartUrl(Entry e)
{
return e->boxartUrl;
}
Entry cloneEntry(Entry e)
{
return newEntry(e->title, e->platform, e->region, e->version, e->author, e->url, e->fileName, e->size, e->boxartUrl, e->extractItems, e->extractItemsCount);
}
struct EntryExtractItem* getEntryExtractItems(Entry e)
{
return e->extractItems;
}
size_t getEntryExtractItemsCount(Entry e)
{
return e->extractItemsCount;
}

24
source/entries.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <calico/types.h>
typedef struct Entry* Entry;
struct EntryExtractItem {
char* outPath;
char* inPath;
};
Entry newEntry(const char* title, const char* platform, char* region, const char* version, const char* author, const char* url, const char* fileName, u64 size, const char* boxartUrl, struct EntryExtractItem* extractItems, size_t extractItemsCount);
void freeEntry(Entry);
char* getEntryTitle(Entry);
char* getEntryPlatform(Entry);
char* getEntryRegion(Entry);
char* getEntryVersion(Entry);
char* getEntryAuthor(Entry);
char* getEntryUrl(Entry);
char* getEntryFileName(Entry);
u64 getEntrySize(Entry);
char* getEntryBoxartUrl(Entry);
Entry cloneEntry(Entry);
struct EntryExtractItem* getEntryExtractItems(Entry);
size_t getEntryExtractItemsCount(Entry);

249
source/gettext.c Normal file
View File

@ -0,0 +1,249 @@
// Based off https://github.com/dborth/libwiigui/blob/master/source/gettext.cpp
#include "gettext.h"
#include "en_lang.h"
#include "it_lang.h"
#include <calico/types.h>
#include <stdlib.h>
#include <string.h>
typedef struct _MSG {
unsigned long id;
char* msgstr;
struct _MSG* next;
} MSG;
static MSG* baseMSG = 0;
#define HASHWORDBITS 32
static inline unsigned long hashString(const char* strParam)
{
unsigned long hval, g;
const char* str = strParam;
hval = 0;
while (*str != '\0') {
hval <<= 4;
hval += (unsigned char)*str++;
g = hval & ((unsigned long)0xf << (HASHWORDBITS - 4));
if (g != 0) {
hval ^= g >> (HASHWORDBITS - 8);
hval ^= g;
}
}
return hval;
}
static char* expandEscape(const char* str)
{
char *retval, *rp;
const char* cp = str;
retval = (char*)malloc(strlen(str) + 1);
if (retval == NULL)
return NULL;
rp = retval;
while (cp[0] != '\0' && cp[0] != '\\')
*rp++ = *cp++;
if (cp[0] != '\0') {
do {
switch (*++cp) {
case '\"':
*rp++ = '\"';
++cp;
break;
case 'a':
*rp++ = '\a';
++cp;
break;
case 'b':
*rp++ = '\b';
++cp;
break;
case 'f':
*rp++ = '\f';
++cp;
break;
case 'n':
*rp++ = '\n';
++cp;
break;
case 'r':
*rp++ = '\r';
++cp;
break;
case 't':
*rp++ = '\t';
++cp;
break;
case 'v':
*rp++ = '\v';
++cp;
break;
case '\\':
*rp = '\\';
++cp;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7': {
size_t ch = *cp++ - '0';
if (*cp >= '0' && *cp <= '7') {
ch *= 8;
ch += *cp++ - '0';
if (*cp >= '0' && *cp <= '7') {
ch *= 8;
ch += *cp++ - '0';
}
}
*rp = ch;
} break;
default:
*rp = '\\';
break;
}
while (cp[0] != '\0' && cp[0] != '\\')
*rp++ = *cp++;
} while (cp[0] != '\0');
}
*rp = '\0';
return retval;
}
static MSG* findMSG(unsigned long id)
{
MSG* msg;
for (msg = baseMSG; msg; msg = msg->next) {
if (msg->id == id)
return msg;
}
return NULL;
}
static MSG* setMSG(const char* msgid, const char* msgstr)
{
unsigned long id = hashString(msgid);
MSG* msg = findMSG(id);
if (!msg) {
msg = (MSG*)malloc(sizeof(MSG));
msg->id = id;
msg->msgstr = NULL;
msg->next = baseMSG;
baseMSG = msg;
}
if (msg) {
if (msgstr) {
if (msg->msgstr)
free(msg->msgstr);
msg->msgstr = expandEscape(msgstr);
}
return msg;
}
return NULL;
}
static void gettextCleanUp(void)
{
while (baseMSG) {
MSG* nextMsg = baseMSG->next;
free(baseMSG->msgstr);
free(baseMSG);
baseMSG = nextMsg;
}
}
static char* memfgets(char* dst, size_t maxlen, char* src)
{
if (!src || !dst || maxlen <= 0)
return NULL;
char* newline = strchr(src, '\n');
if (newline == NULL)
return NULL;
memcpy(dst, src, (newline - src));
dst[(newline - src)] = 0;
return ++newline;
}
static bool loadLanguageFromMemory(const u8* lang_data, size_t lang_size)
{
char line[256];
char* lastID = NULL;
const char *file, *eof;
file = (const char*)lang_data;
eof = file + lang_size;
gettextCleanUp();
while (file && file < eof) {
file = memfgets(line, sizeof(line), (char*)file);
if (!file)
break;
if (line[0] == '#')
continue;
if (strncmp(line, "msgid \"", 7) == 0) {
char *msgid, *end;
if (lastID) {
free(lastID);
lastID = NULL;
}
msgid = &line[7];
end = strrchr(msgid, '"');
if (end && end - msgid > 1) {
*end = 0;
lastID = strdup(msgid);
}
} else if (strncmp(line, "msgstr \"", 8) == 0) {
char *msgstr, *end;
if (lastID == NULL)
continue;
msgstr = &line[8];
end = strrchr(msgstr, '"');
if (end && end - msgstr > 1) {
*end = 0;
setMSG(lastID, msgstr);
}
free(lastID);
lastID = NULL;
}
}
return true;
}
void loadLanguage(LanguageEnum lang)
{
switch (lang) {
case LANG_IT:
loadLanguageFromMemory(it_lang, it_lang_size);
break;
default:
loadLanguageFromMemory(en_lang, en_lang_size);
break;
}
}
const char* gettext(const char* msgid)
{
MSG* msg = findMSG(hashString(msgid));
if (msg && msg->msgstr)
return msg->msgstr;
return msgid;
}

10
source/gettext.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
typedef enum {
LANG_EN,
LANG_IT,
LANGS_COUNT
} LanguageEnum;
void loadLanguage(LanguageEnum lang);
const char* gettext(const char* msg);

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

BIN
source/gfx/brickColor1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

BIN
source/gfx/brickColor2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

1
source/gfx/lButton.grit Normal file
View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

BIN
source/gfx/lButton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

1
source/gfx/rButton.grit Normal file
View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

BIN
source/gfx/rButton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

93
source/gui/box.c Normal file
View File

@ -0,0 +1,93 @@
#include "box.h"
#include <gl2d.h>
struct GuiBox {
size_t width;
size_t height;
size_t posX;
size_t posY;
u16 color;
size_t borderSize;
u16 borderColor;
};
GuiBox newGuiBox(size_t width, size_t height, u16 color)
{
GuiBox gb = malloc(sizeof(struct GuiBox));
gb->width = width;
gb->height = height;
gb->posX = 0;
gb->posY = 0;
gb->color = color;
gb->borderSize = 0;
gb->borderColor = 0;
return gb;
}
void freeGuiBox(GuiBox gb)
{
free(gb);
}
void setGuiBoxWidth(GuiBox gb, size_t width)
{
gb->width = width;
}
void setGuiBoxHeight(GuiBox gb, size_t height)
{
gb->width = height;
}
void setGuiBoxPos(GuiBox gb, size_t posX, size_t posY)
{
gb->posX = posX;
gb->posY = posY;
}
void setGuiBoxBorder(GuiBox gb, size_t borderSize, u16 borderColor)
{
gb->borderSize = borderSize;
gb->borderColor = borderColor;
}
void drawGuiBoxPos(GuiBox gb, size_t posX, size_t posY)
{
if (gb->borderSize && gb->borderColor && gb->color) {
glBoxFilled(
posX,
posY,
posX + gb->width - 1,
posY + gb->height - 1,
gb->borderColor);
glBoxFilled(
posX + gb->borderSize,
posY + gb->borderSize,
posX + gb->width - gb->borderSize - 1,
posY + gb->height - gb->borderSize - 1,
gb->color);
} else if (gb->color) {
glBoxFilled(
posX,
posY,
posX + gb->width - 1,
posY + gb->height - 1,
gb->color);
} else if (gb->borderSize && gb->borderColor) {
for (size_t i = 0; i < gb->borderSize; i++) {
glBox(
posX + i,
posY + i,
posX + gb->width - i - 2,
posY + gb->height - i - 2,
gb->borderColor);
}
}
}
void drawGuiBox(GuiBox gb)
{
drawGuiBoxPos(gb, gb->posX, gb->posY);
}

13
source/gui/box.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <calico/types.h>
typedef struct GuiBox* GuiBox;
GuiBox newGuiBox(size_t width, size_t height, u16 color);
void freeGuiBox(GuiBox gb);
void setGuiBoxWidth(GuiBox, size_t);
void setGuiBoxHeight(GuiBox, size_t);
void setGuiBoxPos(GuiBox, size_t posX, size_t posY);
void setGuiBoxBorder(GuiBox, size_t borderSize, u16 borderColor);
void drawGuiBoxPos(GuiBox, size_t posX, size_t posY);
void drawGuiBox(GuiBox);

161
source/gui/button.c Normal file
View File

@ -0,0 +1,161 @@
#include "button.h"
#include "input.h"
#include <stdlib.h>
struct GuiButton {
size_t width;
size_t height;
size_t posX;
size_t posY;
GuiButtonState state;
GuiBox bg;
GuiBox bgHover;
GuiText label;
GuiImage icon;
GuiImage iconHover;
};
GuiButton newGuiButton(size_t width, size_t height)
{
GuiButton gb = malloc(sizeof(struct GuiButton));
gb->width = width;
gb->height = height;
gb->posX = 0;
gb->posY = 0;
gb->state = GUI_BUTTON_STATE_DEFAULT;
gb->bg = NULL;
gb->bgHover = NULL;
gb->label = NULL;
gb->icon = NULL;
gb->iconHover = NULL;
return gb;
}
void freeGuiButton(GuiButton gb)
{
free(gb);
}
void setGuiButtonWidth(GuiButton gb, size_t width)
{
gb->width = width;
}
void setGuiButtonHeight(GuiButton gb, size_t height)
{
gb->height = height;
}
void setGuiButtonPos(GuiButton gb, size_t posX, size_t posY)
{
gb->posX = posX;
gb->posY = posY;
}
void setGuiButtonState(GuiButton gb, GuiButtonState state)
{
gb->state = state;
}
void resetGuiButtonState(GuiButton gb)
{
gb->state = GUI_BUTTON_STATE_DEFAULT;
}
void setGuiButtonBg(GuiButton gb, GuiBox bg, GuiBox bgHover)
{
gb->bg = bg;
gb->bgHover = bgHover;
}
void setGuiButtonLabel(GuiButton gb, GuiText label)
{
gb->label = label;
}
void setGuiButtonIcon(GuiButton gb, GuiImage icon, GuiImage iconHover)
{
gb->icon = icon;
gb->iconHover = iconHover;
}
size_t getGuiButtonPosX(GuiButton gb)
{
return gb->posX;
}
size_t getGuiButtonPosY(GuiButton gb)
{
return gb->posY;
}
GuiButtonState getGuiButtonState(GuiButton gb)
{
return gb->state;
}
void handleTouchGuiButton(GuiButton gb)
{
if ((gb->state == GUI_BUTTON_STATE_HELD) && (touch.px == 0) && (touch.py == 0))
gb->state = GUI_BUTTON_STATE_CLICKED;
if (!touch.px & !touch.py)
return;
if (((touch.px >= gb->posX) && (touch.px <= (gb->posX + gb->width))) && ((touch.py >= gb->posY) && (touch.py <= (gb->posY + gb->height))))
gb->state = GUI_BUTTON_STATE_HELD;
else if (gb->state == GUI_BUTTON_STATE_HELD)
gb->state = GUI_BUTTON_STATE_DEFAULT;
}
void drawGuiButtonPos(GuiButton gb, size_t posX, size_t posY)
{
if (gb->bg && (gb->state == GUI_BUTTON_STATE_DEFAULT || gb->state == GUI_BUTTON_STATE_CLICKED))
drawGuiBoxPos(gb->bg, posX, posY);
else if (gb->bgHover && (gb->state == GUI_BUTTON_STATE_HELD || gb->state == GUI_BUTTON_STATE_SELECTED))
drawGuiBoxPos(gb->bgHover, posX, posY);
if (gb->icon && (gb->state == GUI_BUTTON_STATE_DEFAULT || gb->state == GUI_BUTTON_STATE_CLICKED))
drawGuiImagePos(gb->icon, posX + (gb->width - getGuiImageWidth(gb->icon)) / 2, posY + (gb->height - getGuiImageHeight(gb->icon)) / 2);
if (gb->iconHover && (gb->state == GUI_BUTTON_STATE_HELD || gb->state == GUI_BUTTON_STATE_SELECTED))
drawGuiImagePos(gb->iconHover, posX + (gb->width - getGuiImageWidth(gb->iconHover)) / 2, posY + (gb->height - getGuiImageHeight(gb->iconHover)) / 2);
if (gb->label) {
size_t labelPosX;
size_t labelPosY;
switch (getGuiTextHAlignment(gb->label)) {
case GUI_TEXT_H_ALIGN_CENTER:
labelPosX = getGuiTextPosX(gb->label) + posX + gb->width / 2;
break;
case GUI_TEXT_H_ALIGN_RIGHT:
labelPosX = getGuiTextPosX(gb->label) + posX + gb->width;
break;
default:
labelPosX = getGuiTextPosX(gb->label) + posX;
break;
}
switch (getGuiTextVAlignment(gb->label)) {
case GUI_TEXT_V_ALIGN_MIDDLE:
labelPosY = getGuiTextPosY(gb->label) + posY + gb->height / 2;
break;
case GUI_TEXT_V_ALIGN_BOTTOM:
labelPosY = getGuiTextPosY(gb->label) + posY + getGuiTextHeight(gb->label);
break;
default:
labelPosY = getGuiTextPosY(gb->label) + posY;
break;
}
drawGuiTextPos(gb->label, labelPosX, labelPosY);
}
}
void drawGuiButton(GuiButton gb)
{
drawGuiButtonPos(gb, gb->posX, gb->posY);
}

29
source/gui/button.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "box.h"
#include "image.h"
#include "text.h"
typedef struct GuiButton* GuiButton;
typedef enum {
GUI_BUTTON_STATE_DEFAULT,
GUI_BUTTON_STATE_HELD,
GUI_BUTTON_STATE_SELECTED,
GUI_BUTTON_STATE_CLICKED
} GuiButtonState;
GuiButton newGuiButton(size_t width, size_t height);
void freeGuiButton(GuiButton);
void setGuiButtonWidth(GuiButton, size_t);
void setGuiButtonPos(GuiButton, size_t posX, size_t posY);
void setGuiButtonState(GuiButton, GuiButtonState);
void resetGuiButtonState(GuiButton);
void setGuiButtonBg(GuiButton, GuiBox bg, GuiBox bgHover);
void setGuiButtonLabel(GuiButton, GuiText);
void setGuiButtonIcon(GuiButton, GuiImage icon, GuiImage iconHover);
size_t getGuiButtonPosX(GuiButton);
size_t getGuiButtonPosY(GuiButton);
GuiButtonState getGuiButtonState(GuiButton);
void handleTouchGuiButton(GuiButton);
void drawGuiButtonPos(GuiButton, size_t posX, size_t posY);
void drawGuiButton(GuiButton);

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View File

@ -0,0 +1,265 @@
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
#pragma once
#define FONTBIG_BITMAP_WIDTH 256
#define FONTBIG_BITMAP_HEIGHT 256
#define FONTBIG_NUM_IMAGES 256
const unsigned int fontBigTexCoords[] = {
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
16, 168, 3, 20, //
4, 168, 3, 20, // !
24, 147, 7, 20, // "
68, 63, 10, 20, // #
120, 84, 9, 20, // $
140, 0, 15, 20, // %
101, 42, 11, 20, // &
252, 126, 3, 20, // '
147, 147, 5, 20, // (
195, 147, 5, 20, // )
40, 147, 7, 20, // *
208, 126, 8, 20, // +
48, 168, 3, 20, // ,
236, 147, 4, 20, // -
40, 168, 3, 20, // .
171, 147, 5, 20, // /
190, 126, 8, 20, // 0
150, 84, 9, 20, // 1
181, 126, 8, 20, // 2
172, 126, 8, 20, // 3
101, 63, 10, 20, // 4
163, 126, 8, 20, // 5
154, 126, 8, 20, // 6
145, 126, 8, 20, // 7
136, 126, 8, 20, // 8
118, 126, 8, 20, // 9
32, 168, 3, 20, // :
36, 168, 3, 20, // ;
180, 84, 9, 20, // <
190, 84, 9, 20, // =
200, 84, 9, 20, // >
210, 84, 9, 20, // ?
74, 0, 16, 20, // @
70, 21, 13, 20, // A
137, 42, 11, 20, // B
173, 42, 11, 20, // C
161, 42, 11, 20, // D
134, 63, 10, 20, // E
220, 84, 9, 20, // F
152, 21, 12, 20, // G
125, 42, 11, 20, // H
56, 168, 3, 20, // I
240, 84, 9, 20, // J
165, 21, 12, 20, // K
0, 105, 9, 20, // L
14, 21, 13, 20, // M
185, 42, 11, 20, // N
217, 21, 12, 20, // O
57, 63, 10, 20, // P
139, 21, 12, 20, // Q
191, 21, 12, 20, // R
90, 63, 10, 20, // S
60, 105, 9, 20, // T
89, 42, 11, 20, // U
149, 42, 11, 20, // V
56, 0, 17, 20, // W
243, 21, 12, 20, // X
77, 42, 11, 20, // Y
100, 126, 8, 20, // Z
165, 147, 5, 20, // [
153, 147, 5, 20, // Backslash
183, 147, 5, 20, // ]
40, 84, 9, 20, // ^
35, 63, 10, 20, // _
211, 147, 4, 20, // `
178, 63, 9, 20, // a
188, 63, 9, 20, // b
37, 126, 8, 20, // c
198, 63, 9, 20, // d
208, 63, 9, 20, // e
91, 147, 6, 20, // f
218, 63, 9, 20, // g
228, 63, 9, 20, // h
20, 168, 3, 20, // i
250, 105, 5, 20, // j
238, 63, 9, 20, // k
12, 168, 3, 20, // l
156, 0, 15, 20, // m
0, 84, 9, 20, // n
10, 84, 9, 20, // o
20, 84, 9, 20, // p
30, 84, 9, 20, // q
126, 147, 6, 20, // r
247, 0, 8, 20, // s
98, 147, 6, 20, // t
50, 84, 9, 20, // u
60, 84, 9, 20, // v
28, 21, 13, 20, // w
70, 84, 9, 20, // x
80, 84, 9, 20, // y
48, 147, 7, 20, // z
16, 147, 7, 20, // {
60, 168, 2, 20, // |
32, 147, 7, 20, // }
90, 84, 9, 20, // ~
210, 84, 9, 20, // ?
100, 84, 9, 20, // €
210, 84, 9, 20, // ?
52, 168, 3, 20, // ‚
19, 126, 8, 20, // ƒ
248, 63, 7, 20, // „
124, 0, 15, 20, // …
46, 126, 8, 20, // †
55, 126, 8, 20, // ‡
105, 147, 6, 20, // ˆ
0, 0, 18, 20, // ‰
112, 63, 10, 20, // Š
216, 147, 4, 20, // ‹
91, 0, 16, 20, // Œ
210, 84, 9, 20, // ?
127, 126, 8, 20, // Ž
210, 84, 9, 20, // ?
210, 84, 9, 20, // ?
8, 168, 3, 20, // ‘
44, 168, 3, 20, // ’
0, 147, 7, 20, // “
8, 147, 7, 20, // ”
189, 147, 5, 20, // •
10, 126, 8, 20, // –
19, 0, 18, 20, // —
133, 147, 6, 20, // ˜
108, 0, 15, 20, // ™
64, 126, 8, 20, // š
206, 147, 4, 20, // ›
172, 0, 15, 20, // œ
210, 84, 9, 20, // ?
244, 126, 7, 20, // ž
197, 42, 11, 20, // Ÿ
24, 168, 3, 20, //  
28, 168, 3, 20, // ¡
73, 126, 8, 20, // ¢
82, 126, 8, 20, // £
167, 63, 10, 20, // ¤
230, 84, 9, 20, // ¥
63, 168, 2, 20, // ¦
109, 126, 8, 20, // §
140, 147, 6, 20, // ¨
204, 21, 12, 20, // ©
56, 147, 6, 20, // ª
199, 126, 8, 20, // «
140, 84, 9, 20, // ¬
241, 147, 4, 20, // ­
178, 21, 12, 20, // ®
123, 63, 10, 20, // ¯
77, 147, 6, 20, // °
217, 126, 8, 20, // ±
159, 147, 5, 20, // ²
250, 84, 5, 20, // ³
201, 147, 4, 20, // ´
226, 126, 8, 20, // µ
130, 84, 9, 20, // ¶
251, 147, 3, 20, // ·
0, 168, 3, 20, // ¸
84, 147, 6, 20, // ¹
177, 147, 5, 20, // º
28, 126, 8, 20, // »
188, 0, 14, 20, // ¼
218, 0, 14, 20, // ½
0, 21, 13, 20, // ¾
110, 84, 9, 20, // ¿
233, 0, 13, 20, // À
112, 21, 13, 20, // Á
98, 21, 13, 20, // Â
84, 21, 13, 20, // Ã
56, 21, 13, 20, // Ä
42, 21, 13, 20, // Å
38, 0, 17, 20, // Æ
113, 42, 11, 20, // Ç
245, 42, 10, 20, // È
24, 63, 10, 20, // É
46, 63, 10, 20, // Ê
79, 63, 10, 20, // Ë
221, 147, 4, 20, // Ì
226, 147, 4, 20, // Í
63, 147, 6, 20, // Î
70, 147, 6, 20, // Ï
126, 21, 12, 20, // Ð
65, 42, 11, 20, // Ñ
0, 42, 12, 20, // Ò
13, 42, 12, 20, // Ó
26, 42, 12, 20, // Ô
39, 42, 12, 20, // Õ
52, 42, 12, 20, // Ö
170, 84, 9, 20, // ×
230, 21, 12, 20, // Ø
209, 42, 11, 20, // Ù
221, 42, 11, 20, // Ú
233, 42, 11, 20, // Û
0, 63, 11, 20, // Ü
12, 63, 11, 20, // Ý
145, 63, 10, 20, // Þ
156, 63, 10, 20, // ß
10, 105, 9, 20, // à
20, 105, 9, 20, // á
30, 105, 9, 20, // â
40, 105, 9, 20, // ã
160, 84, 9, 20, // ä
50, 105, 9, 20, // å
203, 0, 14, 20, // æ
91, 126, 8, 20, // ç
80, 105, 9, 20, // è
90, 105, 9, 20, // é
100, 105, 9, 20, // ê
110, 105, 9, 20, // ë
246, 147, 4, 20, // ì
231, 147, 4, 20, // í
112, 147, 6, 20, // î
119, 147, 6, 20, // ï
120, 105, 9, 20, // ð
130, 105, 9, 20, // ñ
140, 105, 9, 20, // ò
150, 105, 9, 20, // ó
160, 105, 9, 20, // ô
170, 105, 9, 20, // õ
180, 105, 9, 20, // ö
235, 126, 8, 20, // ÷
190, 105, 9, 20, // ø
200, 105, 9, 20, // ù
210, 105, 9, 20, // ú
220, 105, 9, 20, // û
230, 105, 9, 20, // ü
240, 105, 9, 20, // ý
0, 126, 9, 20, // þ
70, 105, 9, 20, // ÿ
210, 84, 9, 20, // ?
};

View File

@ -0,0 +1 @@
-gb -gB8

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,265 @@
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
#pragma once
#define FONTMEDIUM_BITMAP_WIDTH 256
#define FONTMEDIUM_BITMAP_HEIGHT 256
#define FONTMEDIUM_NUM_IMAGES 256
const unsigned int fontMediumTexCoords[] = {
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
252, 68, 3, 16, //
64, 102, 2, 16, // !
187, 85, 5, 16, // "
135, 34, 8, 16, // #
249, 34, 6, 16, // $
179, 0, 10, 16, // %
45, 34, 8, 16, // &
100, 102, 2, 16, // '
221, 85, 4, 16, // (
241, 85, 4, 16, // )
193, 85, 5, 16, // *
36, 34, 8, 16, // +
85, 102, 2, 16, // ,
252, 17, 3, 16, // -
82, 102, 2, 16, // .
211, 85, 4, 16, // /
49, 85, 6, 16, // 0
42, 85, 6, 16, // 1
35, 85, 6, 16, // 2
168, 68, 6, 16, // 3
152, 51, 7, 16, // 4
28, 85, 6, 16, // 5
245, 68, 6, 16, // 6
238, 68, 6, 16, // 7
231, 68, 6, 16, // 8
224, 68, 6, 16, // 9
97, 102, 2, 16, // :
88, 102, 2, 16, // ;
210, 68, 6, 16, // <
189, 68, 6, 16, // =
182, 68, 6, 16, // >
40, 68, 7, 16, // ?
84, 0, 12, 16, // @
120, 17, 9, 16, // A
81, 34, 8, 16, // B
216, 17, 8, 16, // C
18, 34, 8, 16, // D
153, 34, 7, 16, // E
144, 51, 7, 16, // F
130, 17, 9, 16, // G
189, 17, 8, 16, // H
94, 102, 2, 16, // I
169, 34, 7, 16, // J
140, 17, 9, 16, // K
140, 68, 6, 16, // L
121, 0, 11, 16, // M
243, 17, 8, 16, // N
170, 17, 9, 16, // O
177, 34, 7, 16, // P
150, 17, 9, 16, // Q
27, 34, 8, 16, // R
185, 34, 7, 16, // S
119, 68, 6, 16, // T
54, 34, 8, 16, // U
222, 0, 9, 16, // V
44, 0, 13, 16, // W
212, 0, 9, 16, // X
90, 34, 8, 16, // Y
21, 85, 6, 16, // Z
0, 102, 4, 16, // [
251, 85, 4, 16, // Backslash
246, 85, 4, 16, // ]
0, 85, 6, 16, // ^
63, 34, 8, 16, // _
38, 102, 3, 16, // `
201, 34, 7, 16, // a
209, 34, 7, 16, // b
7, 85, 6, 16, // c
217, 34, 7, 16, // d
225, 34, 7, 16, // e
199, 85, 5, 16, // f
241, 34, 7, 16, // g
0, 51, 7, 16, // h
61, 102, 2, 16, // i
30, 102, 3, 16, // j
8, 51, 7, 16, // k
76, 102, 2, 16, // l
201, 0, 10, 16, // m
193, 34, 7, 16, // n
24, 51, 7, 16, // o
32, 51, 7, 16, // p
40, 51, 7, 16, // q
133, 85, 5, 16, // r
133, 68, 6, 16, // s
205, 85, 5, 16, // t
48, 51, 7, 16, // u
56, 51, 7, 16, // v
145, 0, 11, 16, // w
72, 34, 8, 16, // x
64, 51, 7, 16, // y
91, 85, 5, 16, // z
97, 85, 5, 16, // {
70, 102, 2, 16, // |
115, 85, 5, 16, // }
126, 68, 6, 16, // ~
40, 68, 7, 16, // ?
80, 51, 7, 16, // €
40, 68, 7, 16, // ?
67, 102, 2, 16, // ‚
154, 68, 6, 16, // ƒ
121, 85, 5, 16, // „
168, 0, 10, 16, // …
112, 68, 6, 16, // †
84, 85, 6, 16, // ‡
127, 85, 5, 16, // ˆ
71, 0, 12, 16, // ‰
72, 51, 7, 16, // Š
50, 102, 3, 16, // ‹
30, 0, 13, 16, // Œ
40, 68, 7, 16, // ?
161, 68, 6, 16, // Ž
40, 68, 7, 16, // ?
40, 68, 7, 16, // ?
79, 102, 2, 16, // ‘
103, 102, 2, 16, // ’
139, 85, 5, 16, // “
145, 85, 5, 16, // ”
5, 102, 4, 16, // •
233, 34, 7, 16, // –
0, 0, 14, 16, // —
151, 85, 5, 16, // ˜
58, 0, 12, 16, // ™
77, 85, 6, 16, // š
10, 102, 3, 16, // ›
133, 0, 11, 16, // œ
40, 68, 7, 16, // ?
169, 85, 5, 16, // ž
198, 17, 8, 16, // Ÿ
34, 102, 3, 16, //  
58, 102, 2, 16, // ¡
14, 85, 6, 16, // ¢
175, 68, 6, 16, // £
225, 17, 8, 16, // ¤
234, 17, 8, 16, // ¥
73, 102, 2, 16, // ¦
203, 68, 6, 16, // §
175, 85, 5, 16, // ¨
160, 17, 9, 16, // ©
181, 85, 5, 16, // ª
161, 34, 7, 16, // «
217, 68, 6, 16, // ¬
54, 102, 3, 16, // ­
232, 0, 9, 16, // ®
9, 34, 8, 16, // ¯
216, 85, 4, 16, // °
56, 85, 6, 16, // ±
226, 85, 4, 16, // ²
231, 85, 4, 16, // ³
252, 0, 3, 16, // ´
63, 85, 6, 16, // µ
70, 85, 6, 16, // ¶
91, 102, 2, 16, // ·
14, 102, 3, 16, // ¸
18, 102, 3, 16, // ¹
236, 85, 4, 16, // º
136, 51, 7, 16, // »
157, 0, 10, 16, // ¼
190, 0, 10, 16, // ½
97, 0, 11, 16, // ¾
88, 51, 7, 16, // ¿
100, 17, 9, 16, // À
90, 17, 9, 16, // Á
80, 17, 9, 16, // Â
70, 17, 9, 16, // Ã
60, 17, 9, 16, // Ä
110, 17, 9, 16, // Å
15, 0, 14, 16, // Æ
207, 17, 8, 16, // Ç
96, 51, 7, 16, // È
104, 51, 7, 16, // É
112, 51, 7, 16, // Ê
120, 51, 7, 16, // Ë
42, 102, 3, 16, // Ì
46, 102, 3, 16, // Í
157, 85, 5, 16, // Î
163, 85, 5, 16, // Ï
50, 17, 9, 16, // Ð
0, 34, 8, 16, // Ñ
40, 17, 9, 16, // Ò
30, 17, 9, 16, // Ó
20, 17, 9, 16, // Ô
10, 17, 9, 16, // Õ
0, 17, 9, 16, // Ö
128, 51, 7, 16, // ×
242, 0, 9, 16, // Ø
99, 34, 8, 16, // Ù
180, 17, 8, 16, // Ú
108, 34, 8, 16, // Û
117, 34, 8, 16, // Ü
126, 34, 8, 16, // Ý
160, 51, 7, 16, // Þ
147, 68, 6, 16, // ß
168, 51, 7, 16, // à
176, 51, 7, 16, // á
184, 51, 7, 16, // â
192, 51, 7, 16, // ã
208, 51, 7, 16, // ä
200, 51, 7, 16, // å
109, 0, 11, 16, // æ
196, 68, 6, 16, // ç
16, 51, 7, 16, // è
216, 51, 7, 16, // é
224, 51, 7, 16, // ê
232, 51, 7, 16, // ë
22, 102, 3, 16, // ì
26, 102, 3, 16, // í
103, 85, 5, 16, // î
109, 85, 5, 16, // ï
240, 51, 7, 16, // ð
248, 51, 7, 16, // ñ
0, 68, 7, 16, // ò
8, 68, 7, 16, // ó
16, 68, 7, 16, // ô
24, 68, 7, 16, // õ
32, 68, 7, 16, // ö
144, 34, 8, 16, // ÷
48, 68, 7, 16, // ø
56, 68, 7, 16, // ù
64, 68, 7, 16, // ú
72, 68, 7, 16, // û
80, 68, 7, 16, // ü
88, 68, 7, 16, // ý
96, 68, 7, 16, // þ
104, 68, 7, 16, // ÿ
40, 68, 7, 16, // ?
};

View File

@ -0,0 +1 @@
-gb -gB8

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,265 @@
// Generated by bmfont-to-spriteset (https://github.com/cavv-dev/bmfont-to-spriteset)
#pragma once
#define FONTSMALL_BITMAP_WIDTH 256
#define FONTSMALL_BITMAP_HEIGHT 256
#define FONTSMALL_NUM_IMAGES 256
const unsigned int fontSmallTexCoords[] = {
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
8, 65, 3, 12, //
55, 65, 2, 12, // !
195, 39, 5, 12, // "
201, 39, 5, 12, // #
207, 39, 5, 12, // $
147, 0, 8, 12, // %
230, 0, 7, 12, // &
46, 65, 2, 12, // '
236, 52, 3, 12, // (
220, 52, 3, 12, // )
194, 52, 4, 12, // *
6, 52, 5, 12, // +
43, 65, 2, 12, // ,
252, 26, 3, 12, // -
79, 65, 2, 12, // .
24, 65, 3, 12, // /
36, 52, 5, 12, // 0
70, 26, 6, 12, // 1
48, 52, 5, 12, // 2
66, 52, 5, 12, // 3
105, 26, 6, 12, // 4
72, 52, 5, 12, // 5
78, 52, 5, 12, // 6
84, 52, 5, 12, // 7
96, 52, 5, 12, // 8
102, 52, 5, 12, // 9
82, 65, 2, 12, // :
67, 65, 2, 12, // ;
120, 52, 5, 12, // <
132, 52, 5, 12, // =
105, 39, 5, 12, // >
118, 13, 6, 12, // ?
34, 0, 10, 12, // @
96, 13, 7, 12, // A
125, 13, 6, 12, // B
214, 0, 7, 12, // C
132, 13, 6, 12, // D
111, 39, 5, 12, // E
123, 39, 5, 12, // F
24, 13, 7, 12, // G
146, 13, 6, 12, // H
70, 65, 2, 12, // I
141, 39, 5, 12, // J
160, 13, 6, 12, // K
167, 13, 6, 12, // L
98, 0, 9, 12, // M
174, 13, 6, 12, // N
190, 0, 7, 12, // O
181, 13, 6, 12, // P
206, 0, 7, 12, // Q
244, 13, 6, 12, // R
0, 26, 6, 12, // S
7, 26, 6, 12, // T
28, 26, 6, 12, // U
198, 0, 7, 12, // V
118, 0, 9, 12, // W
42, 26, 6, 12, // X
56, 26, 6, 12, // Y
216, 13, 6, 12, // Z
16, 65, 3, 12, // [
4, 65, 3, 12, // Backslash
244, 52, 3, 12, // ]
159, 39, 5, 12, // ^
112, 26, 6, 12, // _
240, 52, 3, 12, // `
126, 26, 6, 12, // a
133, 26, 6, 12, // b
171, 39, 5, 12, // c
231, 26, 6, 12, // d
104, 13, 6, 12, // e
199, 52, 4, 12, // f
111, 13, 6, 12, // g
139, 13, 6, 12, // h
91, 65, 2, 12, // i
88, 65, 2, 12, // j
35, 26, 6, 12, // k
64, 65, 2, 12, // l
12, 0, 10, 12, // m
119, 26, 6, 12, // n
140, 26, 6, 12, // o
77, 26, 6, 12, // p
188, 13, 6, 12, // q
174, 52, 4, 12, // r
195, 13, 6, 12, // s
251, 13, 4, 12, // t
209, 13, 6, 12, // u
219, 39, 5, 12, // v
88, 0, 9, 12, // w
231, 39, 5, 12, // x
237, 39, 5, 12, // y
243, 39, 5, 12, // z
144, 52, 4, 12, // {
254, 0, 1, 12, // |
149, 52, 4, 12, // }
12, 52, 5, 12, // ~
118, 13, 6, 12, // ?
223, 13, 6, 12, // €
118, 13, 6, 12, // ?
58, 65, 2, 12, // ‚
154, 52, 4, 12, // ƒ
42, 52, 5, 12, // „
56, 0, 10, 12, // …
54, 52, 5, 12, // †
60, 52, 5, 12, // ‡
169, 52, 4, 12, // ˆ
23, 0, 10, 12, // ‰
202, 13, 6, 12, // Š
28, 65, 3, 12, // ‹
45, 0, 10, 12, // Œ
118, 13, 6, 12, // ?
237, 13, 6, 12, // Ž
118, 13, 6, 12, // ?
118, 13, 6, 12, // ?
61, 65, 2, 12, // ‘
40, 65, 2, 12, // ’
0, 52, 5, 12, // “
249, 39, 5, 12, // ”
12, 65, 3, 12, // •
230, 13, 6, 12, // –
0, 0, 11, 12, // —
179, 52, 4, 12, // ˜
78, 0, 9, 12, // ™
63, 26, 6, 12, // š
36, 65, 3, 12, // ›
108, 0, 9, 12, // œ
118, 13, 6, 12, // ?
165, 39, 5, 12, // ž
91, 26, 6, 12, // Ÿ
248, 52, 3, 12, //  
85, 65, 2, 12, // ¡
177, 39, 5, 12, // ¢
153, 39, 5, 12, // £
147, 39, 5, 12, // ¤
153, 13, 6, 12, // ¥
94, 65, 1, 12, // ¦
108, 52, 5, 12, // §
20, 65, 3, 12, // ¨
182, 0, 7, 12, // ©
189, 52, 4, 12, // ª
30, 52, 5, 12, // «
24, 52, 5, 12, // ¬
204, 52, 3, 12, // ­
88, 13, 7, 12, // ®
49, 26, 6, 12, // ¯
216, 52, 3, 12, // °
225, 39, 5, 12, // ±
224, 52, 3, 12, // ²
228, 52, 3, 12, // ³
232, 52, 3, 12, // ´
213, 39, 5, 12, // µ
14, 26, 6, 12, // ¶
49, 65, 2, 12, // ·
52, 65, 2, 12, // ¸
252, 52, 3, 12, // ¹
0, 65, 3, 12, // º
189, 39, 5, 12, // »
156, 0, 8, 12, // ¼
222, 0, 7, 12, // ½
128, 0, 9, 12, // ¾
21, 26, 6, 12, // ¿
238, 0, 7, 12, // À
246, 0, 7, 12, // Á
0, 13, 7, 12, // Â
8, 13, 7, 12, // Ã
16, 13, 7, 12, // Ä
174, 0, 7, 12, // Å
67, 0, 10, 12, // Æ
32, 13, 7, 12, // Ç
117, 39, 5, 12, // È
114, 52, 5, 12, // É
129, 39, 5, 12, // Ê
135, 39, 5, 12, // Ë
73, 65, 2, 12, // Ì
76, 65, 2, 12, // Í
159, 52, 4, 12, // Î
164, 52, 4, 12, // Ï
40, 13, 7, 12, // Ð
98, 26, 6, 12, // Ñ
48, 13, 7, 12, // Ò
56, 13, 7, 12, // Ó
64, 13, 7, 12, // Ô
72, 13, 7, 12, // Õ
80, 13, 7, 12, // Ö
183, 39, 5, 12, // ×
165, 0, 8, 12, // Ø
147, 26, 6, 12, // Ù
154, 26, 6, 12, // Ú
161, 26, 6, 12, // Û
168, 26, 6, 12, // Ü
175, 26, 6, 12, // Ý
182, 26, 6, 12, // Þ
189, 26, 6, 12, // ß
196, 26, 6, 12, // à
203, 26, 6, 12, // á
210, 26, 6, 12, // â
217, 26, 6, 12, // ã
84, 26, 6, 12, // ä
224, 26, 6, 12, // å
138, 0, 8, 12, // æ
18, 52, 5, 12, // ç
238, 26, 6, 12, // è
245, 26, 6, 12, // é
0, 39, 6, 12, // ê
7, 39, 6, 12, // ë
208, 52, 3, 12, // ì
212, 52, 3, 12, // í
184, 52, 4, 12, // î
32, 65, 3, 12, // ï
14, 39, 6, 12, // ð
21, 39, 6, 12, // ñ
28, 39, 6, 12, // ò
35, 39, 6, 12, // ó
42, 39, 6, 12, // ô
49, 39, 6, 12, // õ
56, 39, 6, 12, // ö
90, 52, 5, 12, // ÷
63, 39, 6, 12, // ø
70, 39, 6, 12, // ù
77, 39, 6, 12, // ú
84, 39, 6, 12, // û
91, 39, 6, 12, // ü
126, 52, 5, 12, // ý
98, 39, 6, 12, // þ
138, 52, 5, 12, // ÿ
118, 13, 6, 12, // ?
};

View File

@ -0,0 +1 @@
-gb -gB8

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
-gb -gB8 -gTFF00FF

BIN
source/gui/gfx/shiftKey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

309
source/gui/image.c Normal file
View File

@ -0,0 +1,309 @@
#include "image.h"
#include <gl2d.h>
#include <png.h>
struct GuiImage {
size_t width;
size_t height;
size_t posX;
size_t posY;
int scale;
GuiImageHAlign hAlign;
GuiImageVAlign vAlign;
glImage* image;
int textureId;
int colorTintPalId;
};
void bitmapToRGB5A1(u8* bitmap, size_t width, size_t height, GuiImageTextureType textureType)
{
size_t pixelCount = width * height;
u16* rgb5a1Bitmap = (u16*)bitmap;
for (size_t i = 0; i < pixelCount; i++) {
u8 r = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4)];
u8 g = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4) + 1];
u8 b = bitmap[i * (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB ? 3 : 4) + 2];
u8 a = (textureType == GUI_IMAGE_TEXTURE_TYPE_RGB) ? 255 : bitmap[i * 4 + 3]; // Alpha is 255 if format is RGB
u16 rgb5a1 = 0;
rgb5a1 |= (r >> 3) & 0x1F; // 5 bits for red
rgb5a1 |= ((g >> 3) & 0x1F) << 5; // 5 bits for green
rgb5a1 |= ((b >> 3) & 0x1F) << 10; // 5 bits for blue
rgb5a1 |= (a >> 7) << 15; // 1 bit for alpha (most significant bit of alpha)
rgb5a1Bitmap[i] = rgb5a1;
}
}
bool pngFileToBitmap(const char* filePath, u8** bitmap, size_t* width, size_t* height, GuiImageTextureType* textureType)
{
FILE* fp = fopen(filePath, "rb");
if (!fp)
return false;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
fclose(fp);
return false;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, NULL, NULL);
fclose(fp);
return false;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return false;
}
png_init_io(png, fp);
png_read_info(png, info);
*width = png_get_image_width(png, info);
*height = png_get_image_height(png, info);
// 1024 is the max texture size supported
if (*width > 1024 || *height > 1024) {
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return false;
}
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
// Convert to 8-bit depth if necessary
if (bit_depth == 16)
png_set_strip_16(png);
else if (bit_depth < 8)
png_set_packing(png);
// Convert palette images to RGB
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
// Convert grayscale images to RGB
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
// Add alpha channel if necessary
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
// Convert transparency to alpha
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
png_read_update_info(png, info);
*bitmap = (u8*)malloc(png_get_rowbytes(png, info) * (*height));
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * (*height));
for (size_t y = 0; y < *height; y++)
row_pointers[y] = *bitmap + y * png_get_rowbytes(png, info);
png_read_image(png, row_pointers);
*textureType = GUI_IMAGE_TEXTURE_TYPE_RGBA;
bitmapToRGB5A1(*bitmap, *width, *height, *textureType);
fclose(fp);
png_destroy_read_struct(&png, &info, NULL);
free(row_pointers);
return true;
}
// Calculate closest GL_TEXTURE_SIZE_ENUM
#define calculateGlTextureSizeEnum(x) \
((x) <= 8 ? TEXTURE_SIZE_8 \
: (x) <= 16 ? TEXTURE_SIZE_16 \
: (x) <= 32 ? TEXTURE_SIZE_32 \
: (x) <= 64 ? TEXTURE_SIZE_64 \
: (x) <= 128 ? TEXTURE_SIZE_128 \
: (x) <= 256 ? TEXTURE_SIZE_256 \
: (x) <= 512 ? TEXTURE_SIZE_512 \
: (x) <= 1024 ? TEXTURE_SIZE_1024 \
: 0)
GuiImage newGuiImage(const unsigned* bitmap, const u16* pal, size_t width, size_t height, size_t bitmapWidth, size_t bitmapHeight, size_t resizeWidth, size_t resizeHeight, GuiImageTextureType textureType)
{
GuiImage gi = malloc(sizeof(struct GuiImage));
gi->width = width;
gi->height = height;
gi->scale = 1 << 12;
gi->hAlign = GUI_IMAGE_H_ALIGN_LEFT;
gi->vAlign = GUI_IMAGE_V_ALIGN_TOP;
gi->posX = 0;
gi->posY = 0;
gi->image = malloc(sizeof(glImage));
gi->colorTintPalId = 0;
size_t glTextureSizeWidth = calculateGlTextureSizeEnum(bitmapWidth);
size_t glTextureSizeHeight = calculateGlTextureSizeEnum(bitmapHeight);
GL_TEXTURE_TYPE_ENUM glTextureType;
switch (textureType) {
case GUI_IMAGE_TEXTURE_TYPE_RGB:
glTextureType = GL_RGB;
break;
case GUI_IMAGE_TEXTURE_TYPE_RGBA:
glTextureType = GL_RGBA;
break;
default:
glTextureType = GL_RGB256;
break;
}
gi->textureId = glLoadTileSet(
gi->image,
width,
height,
bitmapWidth,
bitmapHeight,
glTextureType,
glTextureSizeWidth,
glTextureSizeHeight,
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
(textureType == GUI_IMAGE_TEXTURE_TYPE_RGB256 ? 256 : 0),
pal,
(u8*)bitmap);
// Set scale to fit inside resized width and height
if (resizeWidth || resizeHeight) {
double scaleFactor = 1.0;
if (resizeWidth && !resizeHeight) {
scaleFactor = (double)resizeWidth / width;
gi->width = resizeWidth;
gi->height *= scaleFactor;
} else if (!resizeWidth && resizeHeight) {
scaleFactor = (double)resizeHeight / height;
gi->height = resizeHeight;
gi->width *= scaleFactor;
} else {
if (resizeWidth <= resizeHeight) {
scaleFactor = (double)resizeWidth / width;
gi->width = resizeWidth;
gi->height *= scaleFactor;
} else {
scaleFactor = (double)resizeHeight / height;
gi->height = resizeHeight;
gi->width *= scaleFactor;
}
}
gi->scale *= scaleFactor;
}
return gi;
}
GuiImage newGuiImagePNG(const char* filePath, size_t resizeWidth, size_t resizeHeight)
{
unsigned* bitmap = NULL;
GuiImageTextureType textureType;
size_t width, height;
if (!pngFileToBitmap(filePath, (u8**)&bitmap, &width, &height, &textureType))
return NULL;
GuiImage gi = newGuiImage(bitmap, NULL, width, height, width, height, resizeWidth, resizeHeight, textureType);
free(bitmap);
return gi;
}
void freeGuiImage(GuiImage gi)
{
glDeleteTextures(1, &gi->textureId);
if (gi->colorTintPalId)
glDeleteTextures(1, &gi->colorTintPalId);
free(gi->image);
free(gi);
}
void setGuiImagePos(GuiImage gi, size_t posX, size_t posY)
{
gi->posX = posX;
gi->posY = posY;
}
void setGuiImageAlign(GuiImage gi, GuiImageHAlign hAlignment, GuiImageVAlign vAlignment)
{
gi->hAlign = hAlignment;
gi->vAlign = vAlignment;
}
void setGuiImageColorTint(GuiImage gi, u16 color)
{
u16 colorTintPal[256];
for (u16 i = 0; i < 256; i++)
colorTintPal[i] = color;
glGenTextures(1, &gi->colorTintPalId);
glBindTexture(0, gi->colorTintPalId);
glColorTableEXT(0, 0, 256, 0, 0, colorTintPal);
}
size_t getGuiImageWidth(GuiImage gi)
{
return gi->width;
}
size_t getGuiImageHeight(GuiImage gi)
{
return gi->height;
}
void drawGuiImagePos(GuiImage gi, size_t posX, size_t posY)
{
size_t x;
size_t y;
switch (gi->hAlign) {
case GUI_IMAGE_H_ALIGN_CENTER:
x = posX - gi->width / 2;
break;
case GUI_IMAGE_H_ALIGN_RIGHT:
x = posX - gi->width;
break;
default:
x = posX;
break;
}
switch (gi->vAlign) {
case GUI_IMAGE_V_ALIGN_MIDDLE:
y = posY - gi->height / 2;
break;
case GUI_IMAGE_V_ALIGN_BOTTOM:
y = posY + gi->height;
break;
default:
y = posY;
break;
}
glSetActiveTexture(gi->textureId);
if (gi->colorTintPalId)
glAssignColorTable(0, gi->colorTintPalId);
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
glSpriteScale(x, y, gi->scale, GL_FLIP_NONE, gi->image);
}
void drawGuiImage(GuiImage gi)
{
drawGuiImagePos(gi, gi->posX, gi->posY);
}

33
source/gui/image.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <calico/types.h>
typedef struct GuiImage* GuiImage;
typedef enum {
GUI_IMAGE_TEXTURE_TYPE_RGB,
GUI_IMAGE_TEXTURE_TYPE_RGBA,
GUI_IMAGE_TEXTURE_TYPE_RGB256
} GuiImageTextureType;
typedef enum {
GUI_IMAGE_H_ALIGN_LEFT,
GUI_IMAGE_H_ALIGN_CENTER,
GUI_IMAGE_H_ALIGN_RIGHT
} GuiImageHAlign;
typedef enum {
GUI_IMAGE_V_ALIGN_TOP,
GUI_IMAGE_V_ALIGN_MIDDLE,
GUI_IMAGE_V_ALIGN_BOTTOM
} GuiImageVAlign;
GuiImage newGuiImage(const unsigned* bitmap, const u16* pal, size_t width, size_t height, size_t bitmapWidth, size_t bitmapHeight, size_t resizeWidth, size_t resizeHeight, GuiImageTextureType textureType);
GuiImage newGuiImagePNG(const char* filePath, size_t resizeWidth, size_t resizeHeight);
void freeGuiImage(GuiImage);
void setGuiImagePos(GuiImage, size_t posX, size_t posY);
void setGuiImageAlign(GuiImage, GuiImageHAlign, GuiImageVAlign);
void setGuiImageColorTint(GuiImage, u16 color);
size_t getGuiImageWidth(GuiImage);
size_t getGuiImageHeight(GuiImage);
void drawGuiImagePos(GuiImage, size_t posX, size_t posY);
void drawGuiImage(GuiImage);

12
source/gui/input.c Normal file
View File

@ -0,0 +1,12 @@
#include "input.h"
u32 pressed;
touchPosition touch;
void updateInput(void)
{
scanKeys();
pressed = keysDown();
touchRead(&touch);
}

7
source/gui/input.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <nds/arm9/input.h>
extern u32 pressed;
extern touchPosition touch;
void updateInput(void);

156
source/gui/keyboard.c Normal file
View File

@ -0,0 +1,156 @@
#include "keyboard.h"
#include "backspaceKey.h"
#include "shiftKey.h"
#include <stdlib.h>
struct GuiKeyboard {
size_t posX;
size_t posY;
struct GuiKeyboardKey* keys;
bool shiftPressed;
};
#define KEYS_COUNT 43
void setKeys(GuiKeyboard gk)
{
const char* chars;
if (!gk->shiftPressed)
chars = "1234567890\0qwertyuiop@asdfghjkl\0zxcvbnm-. \0";
else
chars = "1234567890\0QWERTYUIOP+ASDFGHJKL\0ZXCVBNM_/ \0";
for (size_t i = 0; i < KEYS_COUNT; i++) {
gk->keys[i].c = chars[i];
gk->keys[i].extraKey = GUI_KEYBOARD_EXTRA_KEY_NONE;
char text[2] = { chars[i], '\0' };
setGuiTextText(gk->keys[i].label, text);
}
gk->keys[10].extraKey = GUI_KEYBOARD_EXTRA_KEY_BACKSPACE;
gk->keys[31].extraKey = GUI_KEYBOARD_EXTRA_KEY_SHIFT;
}
void setKeysPos(GuiKeyboard gk)
{
// First row
for (size_t i = 0; i < 11; i++)
setGuiButtonPos(gk->keys[i].btn, gk->posX + i * 23, gk->posY);
// Second row
for (size_t i = 11; i < 22; i++)
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 11) * 23 + 1, gk->posY + 23);
// Third row
for (size_t i = 22; i < 32; i++)
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 22) * 23 + 11, gk->posY + 23 * 2);
// Fourth row
for (size_t i = 32; i < 41; i++)
setGuiButtonPos(gk->keys[i].btn, gk->posX + (i - 32) * 23 + 22, gk->posY + 23 * 3);
// Space
setGuiButtonPos(gk->keys[41].btn, gk->posX + 70, gk->posY + 23 * 4);
}
void setKeysShift(GuiKeyboard gk)
{
gk->shiftPressed = (gk->shiftPressed ? false : true);
setKeys(gk);
}
GuiKeyboard newGuiKeyboard(u16 textColor, u16 hoverColor)
{
GuiKeyboard gk = malloc(sizeof(struct GuiKeyboard));
gk->posX = 0;
gk->posY = 0;
gk->keys = malloc(sizeof(struct GuiKeyboardKey) * KEYS_COUNT);
gk->shiftPressed = false;
// All keys
for (size_t i = 0; i < KEYS_COUNT; i++) {
gk->keys[i].bg = newGuiBox(23, 23, 0);
gk->keys[i].bgHover = newGuiBox(23, 23, 0);
setGuiBoxBorder(gk->keys[i].bgHover, 1, hoverColor);
gk->keys[i].label = newGuiText("", GUI_TEXT_SIZE_MEDIUM, textColor);
setGuiTextAlignment(gk->keys[i].label, GUI_TEXT_H_ALIGN_CENTER, GUI_TEXT_V_ALIGN_MIDDLE);
gk->keys[i].btn = newGuiButton(23, 23);
setGuiButtonBg(gk->keys[i].btn, gk->keys[i].bg, gk->keys[i].bgHover);
setGuiButtonLabel(gk->keys[i].btn, gk->keys[i].label);
}
// Space
setGuiBoxWidth(gk->keys[41].bg, 115);
setGuiBoxBorder(gk->keys[41].bg, 1, textColor);
setGuiBoxWidth(gk->keys[41].bgHover, 115);
setGuiButtonWidth(gk->keys[41].btn, 115);
// Backspace
gk->keys[10].icon = newGuiImage(backspaceKeyBitmap, backspaceKeyPal, 15, 11, 16, 16, 0, 0, GUI_IMAGE_TEXTURE_TYPE_RGB256);
setGuiImageColorTint(gk->keys[10].icon, textColor);
setGuiButtonIcon(gk->keys[10].btn, gk->keys[10].icon, gk->keys[10].icon);
// Shift
gk->keys[31].icon = newGuiImage(shiftKeyBitmap, shiftKeyPal, 11, 13, 16, 16, 0, 0, GUI_IMAGE_TEXTURE_TYPE_RGB256);
setGuiImageColorTint(gk->keys[31].icon, textColor);
setGuiButtonIcon(gk->keys[31].btn, gk->keys[31].icon, gk->keys[31].icon);
setKeysPos(gk);
setKeys(gk);
return gk;
}
void freeGuiKeyboard(GuiKeyboard gk)
{
for (size_t i = 0; i < KEYS_COUNT; i++) {
freeGuiBox(gk->keys[i].bg);
freeGuiBox(gk->keys[i].bgHover);
freeGuiText(gk->keys[i].label);
freeGuiButton(gk->keys[i].btn);
}
freeGuiImage(gk->keys[10].icon); // Backspace
freeGuiImage(gk->keys[31].icon); // Shift
free(gk->keys);
free(gk);
}
void setGuiKeyboardPos(GuiKeyboard gk, size_t posX, size_t posY)
{
gk->posX = posX;
gk->posY = posY;
setKeysPos(gk);
}
struct GuiKeyboardKey getGuiKeyboardPressed(GuiKeyboard gk)
{
for (size_t i = 0; i < KEYS_COUNT; i++) {
if (getGuiButtonState(gk->keys[i].btn) != GUI_BUTTON_STATE_CLICKED)
continue;
if (i == 31) // Shift
setKeysShift(gk);
resetGuiButtonState(gk->keys[i].btn);
return gk->keys[i];
}
return gk->keys[KEYS_COUNT - 1]; // None key
}
void drawGuiKeyboard(GuiKeyboard gk)
{
for (size_t i = 0; i < KEYS_COUNT; i++) {
handleTouchGuiButton(gk->keys[i].btn);
drawGuiButton(gk->keys[i].btn);
}
}

26
source/gui/keyboard.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "button.h"
typedef struct GuiKeyboard* GuiKeyboard;
typedef enum {
GUI_KEYBOARD_EXTRA_KEY_NONE,
GUI_KEYBOARD_EXTRA_KEY_BACKSPACE,
GUI_KEYBOARD_EXTRA_KEY_SHIFT
} GuiKeyboardExtraKey;
struct GuiKeyboardKey {
GuiBox bg;
GuiBox bgHover;
GuiText label;
GuiButton btn;
GuiImage icon;
char c;
GuiKeyboardExtraKey extraKey;
};
GuiKeyboard newGuiKeyboard(u16 textColor, u16 hoverColor);
void freeGuiKeyboard(GuiKeyboard);
void setGuiKeyboardPos(GuiKeyboard, size_t posX, size_t posY);
struct GuiKeyboardKey getGuiKeyboardPressed(GuiKeyboard);
void drawGuiKeyboard(GuiKeyboard);

67
source/gui/progressbar.c Normal file
View File

@ -0,0 +1,67 @@
#include "progressbar.h"
#include <gl2d.h>
struct GuiProgressBar {
size_t width;
size_t height;
size_t posX;
size_t posY;
u16 bgColor;
u16 progressColor;
u8 percent;
};
GuiProgressbar newGuiProgressbar(size_t width, size_t height, u16 bgColor, u16 progressColor)
{
GuiProgressbar gp = malloc(sizeof(struct GuiProgressBar));
gp->width = width;
gp->height = height;
gp->posX = 0;
gp->posY = 0;
gp->bgColor = bgColor;
gp->progressColor = progressColor;
gp->percent = 0;
return gp;
}
void freeGuiProgressbar(GuiProgressbar gp)
{
free(gp);
}
void setGuiProgressbarPos(GuiProgressbar gp, size_t posX, size_t posY)
{
gp->posX = posX;
gp->posY = posY;
}
void setGuiProgressbarPercent(GuiProgressbar gp, u8 percent)
{
gp->percent = percent;
}
void drawGuiProgressbarPos(GuiProgressbar gp, size_t posX, size_t posY)
{
glBoxFilled(
posX,
posY,
posX + gp->width - 1,
posY + gp->height - 1,
gp->bgColor);
if (gp->percent) {
glBoxFilled(
posX,
posY,
posX + (gp->width * gp->percent / 100) - 1,
posY + gp->height - 1,
gp->progressColor);
}
}
void drawGuiProgressbar(GuiProgressbar gp)
{
drawGuiProgressbarPos(gp, gp->posX, gp->posY);
}

11
source/gui/progressbar.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <calico/types.h>
typedef struct GuiProgressBar* GuiProgressbar;
GuiProgressbar newGuiProgressbar(size_t width, size_t height, u16 bgColor, u16 progressColor);
void freeGuiProgressbar(GuiProgressbar);
void setGuiProgressbarPos(GuiProgressbar, size_t posX, size_t posY);
void setGuiProgressbarPercent(GuiProgressbar, u8);
void drawGuiProgressbarPos(GuiProgressbar, size_t posX, size_t posY);
void drawGuiProgressbar(GuiProgressbar);

318
source/gui/screen.c Normal file
View File

@ -0,0 +1,318 @@
#include "screen.h"
#include "button.h"
#include "input.h"
#include "keyboard.h"
#include "progressbar.h"
#include <gl2d.h>
#include <nds.h>
struct ElementNode {
void* value;
GuiElementType type;
struct ElementNode* next;
};
struct ElementList {
struct ElementNode* head;
struct ElementNode* tail;
struct ElementNode* selected;
size_t size;
};
struct GuiScreen {
GuiScreenLcd lcd;
bool dpadNavigate;
struct ElementList* elements;
};
GuiScreen activeTopScreen = NULL;
GuiScreen activeBottomScreen = NULL;
GuiScreenLcd targetLcd = GUI_SCREEN_LCD_TOP;
GuiScreen newGuiScreen(GuiScreenLcd lcd)
{
GuiScreen gs = malloc(sizeof(struct GuiScreen));
gs->lcd = lcd;
gs->dpadNavigate = true;
struct ElementList* el = malloc(sizeof(struct ElementList));
el->head = NULL;
el->tail = NULL;
el->selected = NULL;
el->size = 0;
gs->elements = el;
return gs;
}
void freeGuiScreen(GuiScreen gs)
{
struct ElementNode* curr = gs->elements->head;
while (curr != NULL) {
struct ElementNode* next = curr->next;
free(curr);
curr = next;
}
free(gs->elements);
free(gs);
}
void setGuiScreenDpadNavigate(GuiScreen gs, bool dpadNavigate)
{
gs->dpadNavigate = dpadNavigate;
}
void addToGuiScreen(GuiScreen gs, void* element, GuiElementType type)
{
struct ElementNode* en = malloc(sizeof(struct ElementNode));
en->value = element;
en->type = type;
en->next = NULL;
if (gs->elements->head == NULL) {
gs->elements->head = en;
gs->elements->tail = en;
} else {
gs->elements->tail->next = en;
gs->elements->tail = en;
}
gs->elements->size++;
}
void removeFromGuiScreen(GuiScreen gs, void* element)
{
struct ElementNode* prev = NULL;
struct ElementNode* curr = gs->elements->head;
while (curr != NULL) {
if (curr->value == element) {
if (prev == NULL)
gs->elements->head = curr->next;
else
prev->next = curr->next;
if (curr == gs->elements->tail)
gs->elements->tail = prev;
free(curr);
gs->elements->size--;
break;
}
prev = curr;
curr = curr->next;
}
}
void drawGuiScreen(GuiScreen gs)
{
glBegin2D();
struct ElementNode* curr;
curr = gs->elements->head;
while (curr != NULL) {
switch (curr->type) {
case GUI_ELEMENT_TYPE_BOX:
drawGuiBox(curr->value);
break;
case GUI_ELEMENT_TYPE_IMAGE:
drawGuiImage(curr->value);
break;
case GUI_ELEMENT_TYPE_BUTTON:
if (gs->lcd == GUI_SCREEN_LCD_BOTTOM)
handleTouchGuiButton(curr->value);
drawGuiButton(curr->value);
break;
case GUI_ELEMENT_TYPE_TEXT:
drawGuiText(curr->value);
break;
case GUI_ELEMENT_TYPE_PROGRESSBAR:
drawGuiProgressbar(curr->value);
break;
case GUI_ELEMENT_TYPE_KEYBOARD:
drawGuiKeyboard(curr->value);
break;
}
curr = curr->next;
}
glEnd2D();
}
void setActiveScreens(GuiScreen topScreen, GuiScreen bottomScreen)
{
activeTopScreen = topScreen;
activeBottomScreen = bottomScreen;
}
GuiScreen getActiveTopScreen(void)
{
return activeTopScreen;
}
GuiScreen getActiveBottomScreen(void)
{
return activeBottomScreen;
}
void handleScreensDpadNavigate(void)
{
if ((!activeTopScreen || !activeTopScreen->dpadNavigate) && (!activeBottomScreen || !activeBottomScreen->dpadNavigate))
return;
if (!(pressed & (KEY_DOWN | KEY_UP | KEY_LEFT | KEY_RIGHT | KEY_A)))
return;
GuiButton selected = NULL;
size_t topScreenSize = activeTopScreen ? activeTopScreen->elements->size : 0;
size_t bottomScreenSize = activeBottomScreen ? activeBottomScreen->elements->size : 0;
GuiButton buttons[topScreenSize + bottomScreenSize];
size_t buttonsCount = 0;
struct ElementNode* curr;
// Collect buttons from the top screen
if (activeTopScreen && activeTopScreen->dpadNavigate) {
curr = activeTopScreen->elements->head;
for (; curr != NULL; curr = curr->next) {
if (curr->type != GUI_ELEMENT_TYPE_BUTTON)
continue;
if (getGuiButtonState(curr->value) == GUI_BUTTON_STATE_SELECTED)
selected = curr->value;
buttons[buttonsCount++] = curr->value;
}
}
// Collect buttons from the bottom screen
if (activeBottomScreen && activeBottomScreen->dpadNavigate) {
curr = activeBottomScreen->elements->head;
for (; curr != NULL; curr = curr->next) {
if (curr->type != GUI_ELEMENT_TYPE_BUTTON)
continue;
if (getGuiButtonState(curr->value) == GUI_BUTTON_STATE_SELECTED)
selected = curr->value;
buttons[buttonsCount++] = curr->value;
}
}
if (buttonsCount == 0)
return;
// Find the highest button and select it if no button is selected
if (!selected) {
selected = buttons[0];
for (size_t i = 1; i < buttonsCount; i++) {
size_t selectedPosY = getGuiButtonPosY(selected) + (i < topScreenSize ? 192 : 0);
size_t buttonPosY = getGuiButtonPosY(buttons[i]) + (i < topScreenSize ? 192 : 0);
if (buttonPosY < selectedPosY)
selected = buttons[i];
}
setGuiButtonState(selected, GUI_BUTTON_STATE_SELECTED);
return;
}
// Handle button click
if (pressed & KEY_A) {
if (selected)
setGuiButtonState(selected, GUI_BUTTON_STATE_CLICKED);
return;
}
// Find the closest button according to the pressed key. Account for both screens
GuiButton closest = NULL;
for (size_t i = 0; i < buttonsCount; i++) {
size_t selectedPosY = getGuiButtonPosY(selected) + (i < topScreenSize ? 192 : 0);
size_t buttonPosY = getGuiButtonPosY(buttons[i]) + (i < topScreenSize ? 192 : 0);
size_t selectedPosX = getGuiButtonPosX(selected);
size_t buttonPosX = getGuiButtonPosX(buttons[i]);
if (pressed & KEY_DOWN) {
if (buttonPosY <= selectedPosY)
continue;
if (!closest || buttonPosY < getGuiButtonPosY(closest) + (i < topScreenSize ? 192 : 0))
closest = buttons[i];
} else if (pressed & KEY_UP) {
if (buttonPosY >= selectedPosY)
continue;
if (!closest || buttonPosY > getGuiButtonPosY(closest) + (i < topScreenSize ? 192 : 0))
closest = buttons[i];
} else if (pressed & KEY_LEFT) {
if (buttonPosX >= selectedPosX)
continue;
if (!closest || buttonPosX > getGuiButtonPosX(closest))
closest = buttons[i];
} else if (pressed & KEY_RIGHT) {
if (buttonPosX <= selectedPosX)
continue;
if (!closest || buttonPosX < getGuiButtonPosX(closest))
closest = buttons[i];
}
}
// Update button states
if (closest) {
setGuiButtonState(selected, GUI_BUTTON_STATE_DEFAULT);
setGuiButtonState(closest, GUI_BUTTON_STATE_SELECTED);
}
}
void drawScreens(void)
{
updateInput();
handleScreensDpadNavigate();
// Wait for capture unit to be ready
while (REG_DISPCAPCNT & DCAP_ENABLE) { };
if (activeTopScreen && activeBottomScreen) {
if (targetLcd == GUI_SCREEN_LCD_TOP) {
lcdMainOnBottom();
vramSetBankC(VRAM_C_LCD);
vramSetBankD(VRAM_D_SUB_SPRITE);
REG_DISPCAPCNT = DCAP_BANK(2) | DCAP_ENABLE | DCAP_SIZE(3);
drawGuiScreen(activeTopScreen);
} else {
lcdMainOnTop();
vramSetBankD(VRAM_D_LCD);
vramSetBankC(VRAM_C_SUB_BG);
REG_DISPCAPCNT = DCAP_BANK(3) | DCAP_ENABLE | DCAP_SIZE(3);
drawGuiScreen(activeBottomScreen);
}
// Swap target lcd
targetLcd = targetLcd == GUI_SCREEN_LCD_TOP ? GUI_SCREEN_LCD_BOTTOM : GUI_SCREEN_LCD_TOP;
} else if (activeTopScreen) {
lcdMainOnTop();
vramSetBankD(VRAM_D_LCD);
vramSetBankC(VRAM_C_SUB_BG);
REG_DISPCAPCNT = DCAP_BANK(3) | DCAP_ENABLE | DCAP_SIZE(3);
drawGuiScreen(activeTopScreen);
} else {
lcdMainOnBottom();
vramSetBankC(VRAM_C_LCD);
vramSetBankD(VRAM_D_SUB_SPRITE);
REG_DISPCAPCNT = DCAP_BANK(2) | DCAP_ENABLE | DCAP_SIZE(3);
drawGuiScreen(activeBottomScreen);
}
}

28
source/gui/screen.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <stdbool.h>
typedef struct GuiScreen* GuiScreen;
typedef enum {
GUI_SCREEN_LCD_TOP,
GUI_SCREEN_LCD_BOTTOM
} GuiScreenLcd;
typedef enum {
GUI_ELEMENT_TYPE_BOX,
GUI_ELEMENT_TYPE_IMAGE,
GUI_ELEMENT_TYPE_BUTTON,
GUI_ELEMENT_TYPE_TEXT,
GUI_ELEMENT_TYPE_PROGRESSBAR,
GUI_ELEMENT_TYPE_KEYBOARD
} GuiElementType;
GuiScreen newGuiScreen(GuiScreenLcd);
void freeGuiScreen(GuiScreen);
void addToGuiScreen(GuiScreen, void* element, GuiElementType);
void removeFromGuiScreen(GuiScreen, void* element);
void setGuiScreenDpadNavigate(GuiScreen, bool);
void setActiveScreens(GuiScreen topScreen, GuiScreen bottomScreen);
GuiScreen getActiveTopScreen(void);
GuiScreen getActiveBottomScreen(void);
void drawScreens(void);

377
source/gui/text.c Normal file
View File

@ -0,0 +1,377 @@
#include "text.h"
#include "fontBig_0.h"
#include "fontMedium_0.h"
#include "fontSmall_0.h"
#include "gfx/fontBigUVCoords.h"
#include "gfx/fontMediumUVCoords.h"
#include "gfx/fontSmallUVCoords.h"
#include <gl2d.h>
#include <stdio.h>
glImage fontBig[FONTBIG_NUM_IMAGES];
glImage fontMedium[FONTBIG_NUM_IMAGES];
glImage fontSmall[FONTSMALL_NUM_IMAGES];
int fontBigTextureId;
int fontMediumTextureId;
int fontSmallTextureId;
struct GuiText {
size_t width;
size_t height;
size_t posX;
size_t posY;
char* text;
u16 color;
size_t length;
GuiTextHAlign hAlign;
GuiTextVAlign vAlign;
size_t maxWidth;
size_t maxHeight;
bool wrap;
int textureId;
glImage* font;
};
void initGuiFont(void)
{
fontBigTextureId = glLoadSpriteSet(
fontBig,
FONTBIG_NUM_IMAGES,
fontBigTexCoords,
GL_RGB256,
TEXTURE_SIZE_256,
TEXTURE_SIZE_256,
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
256,
fontBig_0Pal,
(u8*)fontBig_0Bitmap);
fontMediumTextureId = glLoadSpriteSet(
fontMedium,
FONTMEDIUM_NUM_IMAGES,
fontMediumTexCoords,
GL_RGB256,
TEXTURE_SIZE_256,
TEXTURE_SIZE_256,
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
256,
fontMedium_0Pal,
(u8*)fontMedium_0Bitmap);
fontSmallTextureId = glLoadSpriteSet(
fontSmall,
FONTSMALL_NUM_IMAGES,
fontSmallTexCoords,
GL_RGB256,
TEXTURE_SIZE_256,
TEXTURE_SIZE_256,
TEXGEN_OFF | GL_TEXTURE_COLOR0_TRANSPARENT,
256,
fontSmall_0Pal,
(u8*)fontSmall_0Bitmap);
}
GuiText newGuiText(const char* text, GuiTextSize size, u16 color)
{
GuiText gt = malloc(sizeof(struct GuiText));
gt->width = 0;
gt->height = 0;
gt->posX = 0;
gt->posY = 0;
gt->text = NULL;
gt->color = color;
gt->hAlign = GUI_TEXT_H_ALIGN_LEFT;
gt->vAlign = GUI_TEXT_V_ALIGN_TOP;
gt->maxWidth = 0;
gt->maxHeight = 0;
gt->wrap = false;
switch (size) {
case GUI_TEXT_SIZE_BIG:
gt->textureId = fontBigTextureId;
gt->font = fontBig;
break;
case GUI_TEXT_SIZE_MEDIUM:
gt->textureId = fontMediumTextureId;
gt->font = fontMedium;
break;
case GUI_TEXT_SIZE_SMALL:
gt->textureId = fontSmallTextureId;
gt->font = fontSmall;
break;
}
setGuiTextText(gt, text);
return gt;
}
void freeGuiText(GuiText gt)
{
free(gt->text);
free(gt);
}
void updateWH(GuiText gt)
{
size_t x = 0;
size_t y = 0;
size_t lineWidth = 0;
size_t lineStart = 0;
size_t lastSpace = -1;
for (size_t i = 0; i < gt->length; i++) {
glImage currChar = gt->font[gt->text[i] - 1];
lineWidth += currChar.width + 1;
if (gt->text[i] == ' ')
lastSpace = i;
if (gt->maxWidth && (lineWidth > gt->maxWidth || gt->text[i] == '\n')) {
if (lineWidth > gt->maxWidth) {
if (lastSpace != -1 && lastSpace > lineStart) {
i = lastSpace;
lineWidth = 0;
for (size_t j = lineStart; j <= lastSpace; j++)
lineWidth += gt->font[gt->text[j] - 1].width + 1;
} else {
lineWidth -= currChar.width + 1;
i--;
}
}
switch (gt->hAlign) {
case GUI_TEXT_H_ALIGN_LEFT:
x = 0;
break;
case GUI_TEXT_H_ALIGN_CENTER:
x = 0 - lineWidth / 2;
break;
case GUI_TEXT_H_ALIGN_RIGHT:
x = 0 - lineWidth;
break;
}
for (size_t j = lineStart; j <= i; j++) {
currChar = gt->font[gt->text[j] - 1];
if (gt->text[j] != '\n')
x += currChar.width + 1;
}
lineStart = i + 1;
lineWidth = 0;
y += currChar.height + 1;
lastSpace = -1;
if (!gt->wrap || (gt->maxHeight && y + currChar.height > gt->maxHeight)) {
gt->width = x;
gt->height = y;
return;
}
}
}
if (lineStart < gt->length) {
switch (gt->hAlign) {
case GUI_TEXT_H_ALIGN_LEFT:
x = 0;
break;
case GUI_TEXT_H_ALIGN_CENTER:
x = 0 - lineWidth / 2;
break;
case GUI_TEXT_H_ALIGN_RIGHT:
x = 0 - lineWidth;
break;
}
for (size_t j = lineStart; j < gt->length; j++) {
glImage currChar = gt->font[gt->text[j] - 1];
x += currChar.width + 1;
}
}
gt->width = x;
gt->height = y + gt->font[0].height + 1;
}
void setGuiTextPos(GuiText gt, size_t posX, size_t posY)
{
gt->posX = posX;
gt->posY = posY;
}
void setGuiTextText(GuiText gt, const char* text)
{
free(gt->text);
gt->text = strdup(text);
gt->length = strlen(text);
updateWH(gt);
}
void setGuiTextAlignment(GuiText gt, GuiTextHAlign hAlignment, GuiTextVAlign vAlignment)
{
gt->hAlign = hAlignment;
gt->vAlign = vAlignment;
}
void setGuiTextMaxWidth(GuiText gt, size_t maxWidth)
{
gt->maxWidth = maxWidth;
updateWH(gt);
}
void setGuiTextMaxHeight(GuiText gt, size_t maxHeight)
{
gt->maxHeight = maxHeight;
updateWH(gt);
}
void setGuiTextWrap(GuiText gt, bool wrap)
{
gt->wrap = wrap;
updateWH(gt);
}
size_t getGuiTextWidth(GuiText gt)
{
return gt->width;
}
size_t getGuiTextHeight(GuiText gt)
{
return gt->height;
}
size_t getGuiTextPosX(GuiText gt)
{
return gt->posX;
}
size_t getGuiTextPosY(GuiText gt)
{
return gt->posY;
}
GuiTextHAlign getGuiTextHAlignment(GuiText gt)
{
return gt->hAlign;
}
GuiTextVAlign getGuiTextVAlignment(GuiText gt)
{
return gt->vAlign;
}
void drawGuiTextPos(GuiText gt, size_t posX, size_t posY)
{
size_t x = posX;
size_t y = posY;
size_t lineWidth = 0;
size_t lineStart = 0;
size_t lastSpace = -1;
switch (gt->vAlign) {
case GUI_TEXT_V_ALIGN_TOP:
y = posY;
break;
case GUI_TEXT_V_ALIGN_MIDDLE:
y = posY - gt->height / 2;
break;
case GUI_TEXT_V_ALIGN_BOTTOM:
y = posY - gt->height;
break;
}
size_t yStart = y;
glSetActiveTexture(gt->textureId);
glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE | POLY_ID(1));
glColor(gt->color);
for (size_t i = 0; i < gt->length; i++) {
glImage currChar = gt->font[gt->text[i] - 1];
lineWidth += currChar.width + 1;
if (gt->text[i] == ' ')
lastSpace = i;
if (gt->maxWidth && (lineWidth > gt->maxWidth || gt->text[i] == '\n')) {
if (lineWidth > gt->maxWidth) {
if (lastSpace != -1 && lastSpace > lineStart) {
i = lastSpace;
lineWidth = 0;
for (size_t j = lineStart; j <= lastSpace; j++)
lineWidth += gt->font[gt->text[j] - 1].width + 1;
} else {
lineWidth -= currChar.width + 1;
i--;
}
}
switch (gt->hAlign) {
case GUI_TEXT_H_ALIGN_LEFT:
x = posX;
break;
case GUI_TEXT_H_ALIGN_CENTER:
x = posX - lineWidth / 2;
break;
case GUI_TEXT_H_ALIGN_RIGHT:
x = posX - lineWidth;
break;
}
for (size_t j = lineStart; j <= i; j++) {
currChar = gt->font[gt->text[j] - 1];
if (gt->text[j] != '\n') {
glSprite(x, y, GL_FLIP_NONE, &currChar);
x += currChar.width + 1;
}
}
lineStart = i + 1;
lineWidth = 0;
y += currChar.height + 1;
lastSpace = -1;
if (!gt->wrap || (gt->maxHeight && (y - yStart) + currChar.height > gt->maxHeight))
return;
}
}
if (lineStart < gt->length) {
switch (gt->hAlign) {
case GUI_TEXT_H_ALIGN_LEFT:
x = posX;
break;
case GUI_TEXT_H_ALIGN_CENTER:
x = posX - lineWidth / 2;
break;
case GUI_TEXT_H_ALIGN_RIGHT:
x = posX - lineWidth;
break;
}
for (size_t j = lineStart; j < gt->length; j++) {
glImage currChar = gt->font[gt->text[j] - 1];
glSprite(x, y, GL_FLIP_NONE, &currChar);
x += currChar.width + 1;
}
}
glColor(RGB15(31, 31, 31));
}
void drawGuiText(GuiText gt)
{
drawGuiTextPos(gt, gt->posX, gt->posY);
}

40
source/gui/text.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <calico/types.h>
typedef struct GuiText* GuiText;
typedef enum {
GUI_TEXT_SIZE_BIG,
GUI_TEXT_SIZE_MEDIUM,
GUI_TEXT_SIZE_SMALL
} GuiTextSize;
typedef enum {
GUI_TEXT_H_ALIGN_LEFT,
GUI_TEXT_H_ALIGN_CENTER,
GUI_TEXT_H_ALIGN_RIGHT
} GuiTextHAlign;
typedef enum {
GUI_TEXT_V_ALIGN_TOP,
GUI_TEXT_V_ALIGN_MIDDLE,
GUI_TEXT_V_ALIGN_BOTTOM
} GuiTextVAlign;
void initGuiFont(void);
GuiText newGuiText(const char* text, GuiTextSize, u16 color);
void freeGuiText(GuiText);
void setGuiTextText(GuiText, const char*);
void setGuiTextPos(GuiText, size_t posX, size_t posY);
void setGuiTextAlignment(GuiText, GuiTextHAlign, GuiTextVAlign);
void setGuiTextMaxWidth(GuiText, size_t);
void setGuiTextMaxHeight(GuiText, size_t);
void setGuiTextWrap(GuiText, bool);
size_t getGuiTextWidth(GuiText);
size_t getGuiTextHeight(GuiText);
size_t getGuiTextPosX(GuiText);
size_t getGuiTextPosY(GuiText);
GuiTextHAlign getGuiTextHAlignment(GuiText);
GuiTextVAlign getGuiTextVAlignment(GuiText);
void drawGuiTextPos(GuiText, size_t posX, size_t posY);
void drawGuiText(GuiText);

43
source/gui/video.c Normal file
View File

@ -0,0 +1,43 @@
#include "video.h"
#include <gl2d.h>
#include <nds.h>
void initSubSprites(void)
{
oamInit(&oamSub, SpriteMapping_Bmp_2D_256, false);
// Set up a 4x3 grid of 64x64 sprites to cover the screen
u8 id = 0;
for (u8 y = 0; y < 3; y++) {
for (u8 x = 0; x < 4; x++, id++) {
oamSub.oamMemory[id].attribute[0] = ATTR0_BMP | ATTR0_SQUARE | (64 * y);
oamSub.oamMemory[id].attribute[1] = ATTR1_SIZE_64 | (64 * x);
oamSub.oamMemory[id].attribute[2] = ATTR2_ALPHA(1) | (8 * 32 * y) | (8 * x);
}
}
swiWaitForVBlank();
oamUpdate(&oamSub);
}
void initGuiVideo(void)
{
videoSetMode(MODE_0_3D);
videoSetModeSub(MODE_5_2D);
initSubSprites();
bgInitSub(3, BgType_Bmp16, BgSize_B16_256x256, 0, 0);
glScreen2D();
vramSetBankA(VRAM_A_TEXTURE);
vramSetBankB(VRAM_B_TEXTURE);
vramSetBankF(VRAM_F_TEX_PALETTE);
}
void guiLoop(void)
{
glFlush(0);
swiWaitForVBlank();
}

4
source/gui/video.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
void initGuiVideo(void);
void guiLoop(void);

191
source/lang/en.lang Normal file
View File

@ -0,0 +1,191 @@
msgid "Ok"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Yes"
msgstr ""
msgid "No"
msgstr ""
msgid "Browse"
msgstr ""
msgid "Search by title"
msgstr ""
msgid "Browse all"
msgstr ""
msgid "No database loaded or initialized"
msgstr ""
msgid "Page"
msgstr ""
msgid "Previous"
msgstr ""
msgid "Next"
msgstr ""
msgid "Platform"
msgstr ""
msgid "Region"
msgstr ""
msgid "Author"
msgstr ""
msgid "Version"
msgstr ""
msgid "Download"
msgstr ""
msgid "Error"
msgstr ""
msgid "Info"
msgstr ""
msgid "Prompt"
msgstr ""
msgid "Download failed"
msgstr ""
msgid "could not create download directory"
msgstr ""
msgid "bad response status"
msgstr ""
msgid "could not initialize download"
msgstr ""
msgid "could not create file"
msgstr ""
msgid "could not perform download"
msgstr ""
msgid "Download completed"
msgstr ""
msgid "Extracting content..."
msgstr ""
msgid "Extraction failed"
msgstr ""
msgid "could not open file"
msgstr ""
msgid "could not locate file"
msgstr ""
msgid "could not read file"
msgstr ""
msgid "could not write file"
msgstr ""
msgid "Do you wish to extract the content in a separate directory?"
msgstr ""
msgid "Failed to initialize database"
msgstr ""
msgid "download failed"
msgstr ""
msgid "file not found"
msgstr ""
msgid "Database open failed"
msgstr ""
msgid "invalid version"
msgstr ""
msgid "invalid format"
msgstr ""
msgid "Database loaded successfully"
msgstr ""
msgid "entries"
msgstr ""
msgid "Databases"
msgstr ""
msgid "No databases found"
msgstr ""
msgid "Loading database..."
msgstr ""
msgid "Settings"
msgstr ""
msgid "Downloads directory"
msgstr ""
msgid "Use platform-specific directories (E.g. nds, gba)"
msgstr ""
msgid "Color scheme"
msgstr ""
msgid "Check for updates on start"
msgstr ""
msgid "Language"
msgstr ""
msgid "Path can not be empty"
msgstr ""
msgid "Directory does not exist. Do you want to create it?"
msgstr ""
msgid "Failed to create directory"
msgstr ""
msgid "English"
msgstr ""
msgid "Italian"
msgstr ""
msgid "Check for updates"
msgstr ""
msgid "Could not download update"
msgstr ""
msgid "Extracting update files..."
msgstr ""
msgid "Could not extract update files"
msgstr ""
msgid "Update downloaded successfully. Do you want to reboot now?"
msgstr ""
msgid "Checking for updates..."
msgstr ""
msgid "New version found"
msgstr ""
msgid "Update"
msgstr ""
msgid "Ignore"
msgstr ""

188
source/lang/it.lang Normal file
View File

@ -0,0 +1,188 @@
msgid "Ok"
msgstr "Ok"
msgid "Cancel"
msgstr "Annulla"
msgid "Yes"
msgstr "Si"
msgid "No"
msgstr "No"
msgid "Browse"
msgstr "Sfoglia"
msgid "Search by title"
msgstr "Cerca per titolo"
msgid "Browse all"
msgstr "Sfoglia tutto"
msgid "No database loaded or initialized"
msgstr "Nessun database caricato o inizializzato"
msgid "Page"
msgstr "Pagina"
msgid "Previous"
msgstr "Indietro"
msgid "Next"
msgstr "Avanti"
msgid "Platform"
msgstr "Piattaforma"
msgid "Region"
msgstr "Regione"
msgid "Author"
msgstr "Autore"
msgid "Version"
msgstr "Versione"
msgid "Download"
msgstr "Scarica"
msgid "Error"
msgstr "Errore"
msgid "Info"
msgstr "Info"
msgid "Prompt"
msgstr "Richiesta"
msgid "Download failed"
msgstr "Download fallito"
msgid "could not create download directory"
msgstr "impossibile creare la directory di download"
msgid "bad response status"
msgstr "stato di risposta non valido"
msgid "could not initialize download"
msgstr "impossibile inizializzare il download"
msgid "could not create file"
msgstr "impossibile creare il file"
msgid "could not perform download"
msgstr "impossibile eseguire il download"
msgid "Download completed"
msgstr "Download completato"
msgid "Extracting content..."
msgstr "Estraendo il contenuto..."
msgid "Extraction failed"
msgstr "Estrazione fallita"
msgid "could not open file"
msgstr "impossibile aprire il file"
msgid "could not locate file"
msgstr "impossibile trovare il file"
msgid "could not read file"
msgstr "impossibile leggere il file"
msgid "could not write file"
msgstr "impossibile scrivere il file"
msgid "Do you wish to extract the content in a separate directory?"
msgstr "Vuoi estrarre il contenuto in una cartella separata?"
msgid "Failed to initialize database"
msgstr "Inizializzazione database fallita"
msgid "download failed"
msgstr "download fallito"
msgid "file not found"
msgstr "file non trovato"
msgid "Database open failed"
msgstr "Apertura database fallita"
msgid "invalid version"
msgstr "versione non valida"
msgid "invalid format"
msgstr "formato non valido"
msgid "Database loaded successfully"
msgstr "Database caricato correttamente"
msgid "entries"
msgstr "entry"
msgid "Databases"
msgstr "Database"
msgid "No databases found"
msgstr "Nessun database trovato"
msgid "Loading database..."
msgstr "Caricando database..."
msgid "Settings"
msgstr "Impostazioni"
msgid "Downloads directory"
msgstr "Cartella download"
msgid "Use platform-specific directories (E.g. nds, gba)"
msgstr "Usa cartelle specifiche per piattaforma (Es. nds, gba)"
msgid "Color scheme"
msgstr "Schema colore"
msgid "Check for updates on start"
msgstr "Controlla aggiornamenti all'avvio"
msgid "Language"
msgstr "Lingua"
msgid "Path can not be empty"
msgstr "Il percorso non puo' essere vuoto"
msgid "Directory does not exist. Do you want to create it?"
msgstr "La cartella non esiste. Vuoi crearla?"
msgid "Failed to create directory"
msgstr "Impossibile creare la cartella"
msgid "Italian"
msgstr "Italiano"
msgid "Check for updates"
msgstr "Controlla aggiornamenti"
msgid "Could not download update"
msgstr "Impossibile scaricare aggiornamento"
msgid "Extracting update files..."
msgstr "Estraendo file di aggiornamento..."
msgid "Could not extract update files"
msgstr "Impossibile estrarre file di aggiornamento"
msgid "Update downloaded successfully. Do you want to reboot now?"
msgstr "Aggiornamento scaricato con successo. Vuoi riavviare adesso?"
msgid "Checking for updates..."
msgstr "Controllando aggiornamenti..."
msgid "New version found"
msgstr "Nuova versione trovata"
msgid "Update"
msgstr "Aggiorna"
msgid "Ignore"
msgstr "Ignora"

50
source/main.c Normal file
View File

@ -0,0 +1,50 @@
#include "main.h"
#include "config.h"
#include "gui/text.h"
#include "gui/video.h"
#include "menu.h"
#include "networking.h"
#include "settings.h"
#include "utils/filesystem.h"
#include <fat.h>
#include <nds.h>
#include <unistd.h>
Database db = NULL;
void handleInit(bool result, const char* infoMessage, const char* errorMessage, u8 sleepTime, bool exitOnError)
{
iprintf(infoMessage);
if (result) {
iprintf("Ok!\n");
return;
}
iprintf(errorMessage);
sleep(sleepTime);
if (exitOnError)
exit(1);
}
int main(void)
{
consoleDemoInit();
iprintf("\n\t\t\t" APP_NAME " " APP_VERSION "\n\n");
iprintf("Initializing\n\n");
handleInit(fatInitDefault(), "Filesystem...", "\n\nFailed to initialize filesystem\n", 5, true);
handleInit(initNetworking() == NETWORKING_INIT_SUCCESS, "Networking...", "\n\nFailed to initialize networking:\nwifi connection failed\n", 5, true);
handleInit((createDir(APPDATA_DIR) && createDir(CACHE_DIR)), "Directories...", "\n\nFailed to initialize directories\n", 5, true);
handleInit((defaultSettings() && loadSettings()), "Settings...", "\n\nLoaded default settings\n", 3, false);
initGuiVideo();
initGuiFont();
menuBegin(MENU_BROWSE);
return 0;
}

4
source/main.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "database.h"
extern Database db;

1835
source/menu.c Normal file

File diff suppressed because it is too large Load Diff

15
source/menu.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
typedef enum {
MENU_NONE,
MENU_BROWSE,
MENU_RESULTS,
MENU_ENTRY,
MENU_DOWNLOAD,
MENU_DATABASES,
MENU_SETTINGS,
MENU_INFO,
MENU_EXIT
} MenuEnum;
void menuBegin(MenuEnum);

179
source/networking.c Normal file
View File

@ -0,0 +1,179 @@
#include "networking.h"
#include "config.h"
#include <dswifi9.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFFER_SIZE 1024 * 1024 // 1 MiB
#define USER_AGENT APP_NAME "/" APP_VERSION " (DS)"
NetworkingInitStatus initNetworking(void)
{
if (!Wifi_InitDefault(WFC_CONNECT))
return NETWORKING_INIT_ERR_WIFI_CONNECT;
curl_global_init(CURL_GLOBAL_DEFAULT);
return NETWORKING_INIT_SUCCESS;
}
bool stopDownloadSignal = false;
struct WriteData {
FILE* fp;
char* buffer;
size_t bufferPos;
};
size_t writeDataCallback(void* ptr, size_t size, size_t nmemb, void* userdata)
{
struct WriteData* writeData = (struct WriteData*)userdata;
if (stopDownloadSignal)
return -1;
size_t total_size = size * nmemb;
if (writeData->bufferPos + total_size > BUFFER_SIZE) {
fwrite(writeData->buffer, 1, writeData->bufferPos, writeData->fp);
writeData->bufferPos = 0;
}
if (total_size > BUFFER_SIZE) {
fwrite(ptr, 1, total_size, writeData->fp);
} else {
memcpy(writeData->buffer + writeData->bufferPos, ptr, total_size);
writeData->bufferPos += total_size;
}
return total_size;
}
DownloadStatus downloadFile(const char* path, const char* url, size_t (*downloadProgressCallback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t))
{
stopDownloadSignal = false;
CURL* curl = curl_easy_init();
if (!curl)
return DOWNLOAD_ERR_INIT_FAILED;
FILE* fp = fopen(path, "wb");
if (!fp) {
curl_easy_cleanup(curl);
return DOWNLOAD_ERR_FILE_OPEN_FAILED;
}
struct WriteData writeData;
writeData.fp = fp;
writeData.buffer = (char*)malloc(BUFFER_SIZE);
writeData.bufferPos = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeDataCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeData);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7L);
if (downloadProgressCallback) {
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, NULL);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, downloadProgressCallback);
}
CURLcode res = curl_easy_perform(curl);
if (writeData.bufferPos > 0)
fwrite(writeData.buffer, 1, writeData.bufferPos, writeData.fp);
free(writeData.buffer);
fclose(fp);
if (stopDownloadSignal) {
curl_easy_cleanup(curl);
unlink(path);
return DOWNLOAD_STOPPED;
}
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
unlink(path);
return DOWNLOAD_ERR_PERFORM_FAILED;
}
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (httpCode != 200) {
unlink(path);
return DOWNLOAD_ERR_NOT_OK;
}
return DOWNLOAD_SUCCESS;
}
void stopDownload(void)
{
stopDownloadSignal = true;
}
struct MemoryWriteData {
char* result;
size_t bufferSize;
size_t currentPos;
};
size_t memoryWriteCallback(void* ptr, size_t size, size_t nmemb, void* userdata)
{
struct MemoryWriteData* writeData = (struct MemoryWriteData*)userdata;
size_t total_size = size * nmemb;
if (writeData->currentPos + total_size >= writeData->bufferSize) {
total_size = writeData->bufferSize - writeData->currentPos - 1;
}
memcpy(writeData->result + writeData->currentPos, ptr, total_size);
writeData->currentPos += total_size;
writeData->result[writeData->currentPos] = '\0';
return total_size;
}
DownloadStatus downloadToString(char* result, size_t bufferSize, const char* url)
{
CURL* curl = curl_easy_init();
if (!curl)
return DOWNLOAD_ERR_INIT_FAILED;
struct MemoryWriteData writeData;
writeData.result = result;
writeData.bufferSize = bufferSize;
writeData.currentPos = 0;
result[0] = '\0';
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memoryWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeData);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7L);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
return DOWNLOAD_ERR_PERFORM_FAILED;
}
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
curl_easy_cleanup(curl);
if (httpCode != 200)
return DOWNLOAD_ERR_NOT_OK;
return DOWNLOAD_SUCCESS;
}

21
source/networking.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <curl/curl.h>
typedef enum {
NETWORKING_INIT_SUCCESS,
NETWORKING_INIT_ERR_WIFI_CONNECT
} NetworkingInitStatus;
typedef enum {
DOWNLOAD_SUCCESS,
DOWNLOAD_STOPPED,
DOWNLOAD_ERR_NOT_OK,
DOWNLOAD_ERR_INIT_FAILED,
DOWNLOAD_ERR_FILE_OPEN_FAILED,
DOWNLOAD_ERR_PERFORM_FAILED
} DownloadStatus;
NetworkingInitStatus initNetworking(void);
DownloadStatus downloadFile(const char* path, const char* url, size_t (*downloadProgressCallback)(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t));
void stopDownload(void);
DownloadStatus downloadToString(char* result, size_t bufferSize, const char* url);

80
source/settings.c Normal file
View File

@ -0,0 +1,80 @@
#include "settings.h"
#include "config.h"
#include "utils/filesystem.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SETTINGS_FILENAME "settings.cfg"
struct Settings settings;
bool defaultSettings(void)
{
if (dirExists("/roms")) {
sprintf(settings.dlPath, "/roms");
settings.dlUseDirs = true; // The user probably wants to use platform-specific directories if has a roms directory
} else {
sprintf(settings.dlPath, "/");
settings.dlUseDirs = false;
}
settings.colorScheme = COLOR_SCHEME_1;
settings.lang = LANG_EN;
settings.checkUpdateOnStart = true;
return true;
}
bool loadSettings(void)
{
FILE* fp = fopen(APPDATA_DIR "/" SETTINGS_FILENAME, "r");
if (!fp)
return false;
char line[2048];
char name[1024];
char value[1024];
while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\r\n")] = 0;
if (sscanf(line, "%[^=]=%s", name, value) != 2) {
fclose(fp);
return false;
}
int valueInt = atoi(value);
if (strcmp(name, "dlPath") == 0 && dirExists(value))
snprintf(settings.dlPath, sizeof(settings.dlPath), value);
else if (strcmp(name, "useDirs") == 0 && (valueInt == 0 || valueInt == 1))
settings.dlUseDirs = valueInt;
else if (strcmp(name, "colorScheme") == 0 && (valueInt > 0 && valueInt < COLOR_SCHEMES_COUNT + 1))
settings.colorScheme = valueInt - 1;
else if (strcmp(name, "lang") == 0 && (valueInt >= 0 && valueInt < LANGS_COUNT))
settings.lang = valueInt;
else if (strcmp(name, "checkUpdateOnStart") == 0 && (valueInt == 0 || valueInt == 1))
settings.checkUpdateOnStart = valueInt;
}
fclose(fp);
return true;
}
bool saveSettings(void)
{
FILE* fp = fopen(APPDATA_DIR "/" SETTINGS_FILENAME, "w");
if (!fp)
return false;
fprintf(fp, "dlPath=%s\n", settings.dlPath);
fprintf(fp, "useDirs=%d\n", settings.dlUseDirs);
fprintf(fp, "colorScheme=%d\n", settings.colorScheme + 1);
fprintf(fp, "lang=%d\n", settings.lang);
fprintf(fp, "checkUpdateOnStart=%d\n", settings.checkUpdateOnStart);
fclose(fp);
return true;
}

42
source/settings.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include "colors.h"
#include "gettext.h"
#include <sys/syslimits.h>
typedef enum {
SETTING_DLPATH,
SETTING_DLUSEDIRS,
SETTING_COLORSCHEME,
SETTING_CHECKUPDATEONSTART,
SETTING_LANG,
SETTINGS_COUNT
} SettingEnum;
struct Settings {
char dlPath[PATH_MAX];
bool dlUseDirs;
ColorSchemeEnum colorScheme;
LanguageEnum lang;
bool checkUpdateOnStart;
};
extern struct Settings settings;
#define dlUseDirsStr(x) \
((x) == false ? "No" \
: (x) == true ? "Yes" \
: "")
#define langStr(x) \
((x) == LANG_EN ? "English" \
: (x) == LANG_IT ? "Italian" \
: "")
#define checkUpdateOnStartStr(x) \
((x) == false ? "No" \
: (x) == true ? "Yes" \
: "")
bool defaultSettings(void);
bool loadSettings(void);
bool saveSettings(void);

164
source/utils/filesystem.c Normal file
View File

@ -0,0 +1,164 @@
#include "filesystem.h"
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool fileExists(const char* filePath)
{
struct stat statbuf;
if (stat(filePath, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
return true;
else
return false;
}
bool dirExists(const char* dirPath)
{
struct stat statbuf;
if (stat(dirPath, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
return true;
else
return false;
}
bool pathExists(const char* path)
{
return fileExists(path) || dirExists(path);
}
bool deleteFile(const char* filePath)
{
if (unlink(filePath) != 0)
return false;
return true;
}
bool deleteDir(const char* dirPath)
{
DIR* dp = opendir(dirPath);
if (dp == NULL)
return false;
struct dirent* entry;
while ((entry = readdir(dp)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char fullPath[PATH_MAX];
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, entry->d_name);
struct stat statbuf;
if (stat(fullPath, &statbuf) != 0) {
closedir(dp);
return false;
}
if (S_ISDIR(statbuf.st_mode)) {
if (!deleteDir(fullPath)) {
closedir(dp);
return false;
}
} else {
if (!deleteFile(fullPath)) {
closedir(dp);
return false;
}
}
}
closedir(dp);
if (rmdir(dirPath) != 0)
return false;
return true;
}
bool deletePath(const char* path)
{
return deleteFile(path) || deleteDir(path);
}
bool renamePath(const char* oldPath, const char* newPath)
{
if (pathExists(newPath)) {
if (!deletePath(newPath))
return false;
}
if (rename(oldPath, newPath) != 0)
return false;
return true;
}
bool createDir(const char* dirPath)
{
if (mkdir(dirPath, 0755) == 0 || errno == EEXIST)
return true;
else
return false;
}
bool createDirR(const char* dirPath)
{
char tempPath[PATH_MAX];
char* p = NULL;
size_t len;
strncpy(tempPath, dirPath, sizeof(tempPath) - 1);
len = strlen(tempPath);
// Remove trailing slash if present
if (tempPath[len - 1] == '/')
tempPath[len - 1] = '\0';
// Skip device name if present (e.g., "fat:")
p = strchr(tempPath, ':');
if (p != NULL)
p++; // Move past the ':'
else
p = tempPath;
// Iterate through the path and create directories as needed
for (; *p; p++) {
if (*p != '/')
continue;
*p = '\0';
if (!createDir(tempPath))
return false;
*p = '/';
}
// Create the final directory
return createDir(tempPath);
}
void getPathDir(const char* path, char* dir)
{
const char* lastSlash = strrchr(path, '/');
if (!lastSlash) {
dir[0] = '\0';
return;
}
size_t dirLen = lastSlash - path + 1;
strncpy(dir, path, dirLen);
dir[dirLen] = '\0';
}
bool createDirStructure(const char* path)
{
char dirPath[PATH_MAX];
getPathDir(path, dirPath);
return createDirR(dirPath);
}

13
source/utils/filesystem.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <stdbool.h>
bool fileExists(const char* filePath);
bool dirExists(const char* dirPath);
bool pathExists(const char* path);
bool deleteFile(const char* filePath);
bool deleteDir(const char* dirPath);
bool deletePath(const char* path);
bool renamePath(const char* oldPath, const char* newPath);
bool createDir(const char* dirPath);
bool createDirR(const char* dirPath);
bool createDirStructure(const char* path);

4
source/utils/math.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))

76
source/utils/strings.c Normal file
View File

@ -0,0 +1,76 @@
#include "strings.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
void humanizeSize(char* sizeStr, size_t bufferSize, u64 sizeInBytes)
{
const char* suffix[] = { "B", "KiB", "MiB", "GiB" };
u8 suffixesCount = 4;
u8 i = 0;
double dblBytes = sizeInBytes;
if (sizeInBytes > 1024) {
for (i = 0; (sizeInBytes / 1024) > 0 && i < suffixesCount - 1; i++, sizeInBytes /= 1024)
dblBytes = (double)sizeInBytes / 1024;
}
snprintf(sizeStr, bufferSize, "%.01lf %s", dblBytes, suffix[i]);
}
void lowerStr(char* str)
{
for (size_t i = 0; str[i] != '\0'; i++)
str[i] = tolower(str[i]);
}
void removeAccentsStr(char* str)
{
const char normalizedCharMap[] = {
(char)0, (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, (char)32, (char)33, (char)34, (char)35, (char)36, (char)37, (char)38, (char)39, (char)40, (char)41, (char)42, (char)43, (char)44, (char)45, (char)46, (char)47, (char)48, (char)49, (char)50, (char)51, (char)52, (char)53, (char)54, (char)55, (char)56, (char)57, (char)58, (char)59, (char)60, (char)61, (char)62, (char)63, (char)64, (char)65, (char)66, (char)67, (char)68, (char)69, (char)70, (char)71, (char)72, (char)73, (char)74, (char)75, (char)76, (char)77, (char)78, (char)79, (char)80, (char)81, (char)82, (char)83, (char)84, (char)85, (char)86, (char)87, (char)88, (char)89, (char)90, (char)91, (char)92, (char)93, (char)94, (char)95, (char)96, (char)97, (char)98, (char)99, (char)100, (char)101, (char)102, (char)103, (char)104, (char)105, (char)106, (char)107, (char)108, (char)109, (char)110, (char)111, (char)112, (char)113, (char)114, (char)115, (char)116, (char)117, (char)118, (char)119, (char)120, (char)121, (char)122, (char)123, (char)124, (char)125, (char)126, (char)127, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)63, (char)32, (char)33, (char)63, (char)63, (char)63, (char)63, (char)124, (char)63, (char)34, (char)63, (char)97, (char)63, (char)33, (char)63, (char)63, (char)45, (char)63, (char)63, (char)50, (char)51, (char)39, (char)117, (char)80, (char)42, (char)44, (char)49, (char)111, (char)63, (char)63, (char)63, (char)63, (char)63, (char)65, (char)65, (char)65, (char)65, (char)65, (char)65, (char)63, (char)67, (char)69, (char)69, (char)69, (char)69, (char)73, (char)73, (char)73, (char)73, (char)68, (char)78, (char)79, (char)79, (char)79, (char)79, (char)79, (char)120, (char)79, (char)85, (char)85, (char)85, (char)85, (char)89, (char)63, (char)63, (char)97, (char)97, (char)97, (char)97, (char)97, (char)97, (char)63, (char)99, (char)101, (char)101, (char)101, (char)101, (char)105, (char)105, (char)105, (char)105, (char)100, (char)110, (char)111, (char)111, (char)111, (char)111, (char)111, (char)47, (char)111, (char)117, (char)117, (char)117, (char)117, (char)121, (char)63, (char)121
};
// Replace each non-ASCII character with its corresponding in map
size_t i = 0, j = 0;
while (str[i] != '\0') {
u8 c = (u8)str[i];
if (c < 128)
str[j++] = str[i];
else
str[j++] = normalizedCharMap[c];
i++;
}
str[j] = '\0';
}
void safeStr(char* str)
{
removeAccentsStr(str);
// Replace characters not in 'a-zA-Z0-9 ' with '_'
size_t i, j;
for (i = 0, j = 0; str[i]; i++) {
if ((str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= '0' && str[i] <= '9') || (str[i] == ' '))
str[j++] = str[i];
else
str[j++] = '_';
}
str[j] = '\0';
}
void joinPath(char* joinedPath, const char* path1, const char* path2)
{
strcpy(joinedPath, path1);
if (joinedPath[strlen(joinedPath) - 1] != '/')
strcat(joinedPath, "/");
if (path2[0] == '/')
strcat(joinedPath, path2 + 1);
else
strcat(joinedPath, path2);
}

8
source/utils/strings.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include <calico/types.h>
void humanizeSize(char* sizeStr, size_t bufferSize, u64 sizeInBytes);
void lowerStr(char*);
void removeAccentsStr(char*);
void safeStr(char*);
void joinPath(char* joinedPath, const char* path1, const char* path2);