Initial commit
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
build/
|
||||
Kekatsu.elf
|
||||
Kekatsu.nds
|
||||
release/
|
||||
Kekatsu-DS.zip
|
||||
version.txt
|
||||
.vscode/
|
21
LICENSE
Normal 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
@ -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
@ -0,0 +1,93 @@
|
||||
# Kekatsu DS
|
||||
Easy-to-use content downloader for Nintendo DS(i) consoles
|
||||
|
||||
  
|
||||
|
||||
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
resources/screenshots/Kekatsu-DS_1.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/screenshots/Kekatsu-DS_2.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/screenshots/Kekatsu-DS_3.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
158
source/archives.c
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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);
|
1
source/gfx/brickColor1.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/brickColor1.png
Normal file
After Width: | Height: | Size: 957 B |
1
source/gfx/brickColor2.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/brickColor2.png
Normal file
After Width: | Height: | Size: 979 B |
1
source/gfx/lButton.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/lButton.png
Normal file
After Width: | Height: | Size: 373 B |
1
source/gfx/navbarBrowseIcon.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarBrowseIcon.png
Normal file
After Width: | Height: | Size: 239 B |
1
source/gfx/navbarDatabasesIcon.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarDatabasesIcon.png
Normal file
After Width: | Height: | Size: 210 B |
1
source/gfx/navbarInfoIcon.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarInfoIcon.png
Normal file
After Width: | Height: | Size: 207 B |
1
source/gfx/navbarSettingsIcon.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/navbarSettingsIcon.png
Normal file
After Width: | Height: | Size: 255 B |
1
source/gfx/rButton.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gfx/rButton.png
Normal file
After Width: | Height: | Size: 528 B |
93
source/gui/box.c
Normal 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
@ -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
@ -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
@ -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);
|
1
source/gui/gfx/backspaceKey.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gui/gfx/backspaceKey.png
Normal file
After Width: | Height: | Size: 158 B |
265
source/gui/gfx/fontBigUVCoords.h
Normal 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, // ?
|
||||
};
|
1
source/gui/gfx/fontBig_0.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8
|
BIN
source/gui/gfx/fontBig_0.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
265
source/gui/gfx/fontMediumUVCoords.h
Normal 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, // ?
|
||||
};
|
1
source/gui/gfx/fontMedium_0.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8
|
BIN
source/gui/gfx/fontMedium_0.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
265
source/gui/gfx/fontSmallUVCoords.h
Normal 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, // ?
|
||||
};
|
1
source/gui/gfx/fontSmall_0.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8
|
BIN
source/gui/gfx/fontSmall_0.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
1
source/gui/gfx/shiftKey.grit
Normal file
@ -0,0 +1 @@
|
||||
-gb -gB8 -gTFF00FF
|
BIN
source/gui/gfx/shiftKey.png
Normal file
After Width: | Height: | Size: 144 B |
309
source/gui/image.c
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
void initGuiVideo(void);
|
||||
void guiLoop(void);
|
191
source/lang/en.lang
Normal 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
@ -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
@ -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
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "database.h"
|
||||
|
||||
extern Database db;
|
1835
source/menu.c
Normal file
15
source/menu.h
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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);
|