Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
ducalex committed Aug 1, 2023
2 parents 2ffdbce + 0b08b91 commit d53c9e9
Show file tree
Hide file tree
Showing 18 changed files with 501 additions and 151 deletions.
72 changes: 72 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Building Retro-Go

## Prerequisites
You will need a working installation of [esp-idf](https://docs.espressif.com/projects/esp-idf/en/release-v4.3/esp32/get-started/index.html#get-started-get-prerequisites). Only versions 4.1 to 4.4 are supported. Support for 5.0 is coming soon.

_Note: As of retro-go 1.35, I use 4.3.3. Version 4.1.x was used for 1.20 to 1.34 versions._

### ESP-IDF Patches
Patching esp-idf may be required for full functionality. Patches are located in `tools/patches` and can be applied to your global esp-idf installation, they will not break your other projects/devices.
- `sdcard-fix`: This patch is mandatory for the ODROID-GO (and clones).
- `panic-hook`: This is to help users report bugs, see `Capturing crash logs` below for more details. The patch is optional but recommended.
- `enable-exfat`: Enable exFAT support. I don't recommended it but it works if you need it.


## Obtaining the code

You can simply download a zip from the project's front page and extract it, but using git is better for development.

There are generally two active git branches on retro-go:
- `master` contains the code form the most recent release and is usually tested and known to be working
- `dev` contains code in development that will be merged to master upon the next release and is often untested

`git clone -b <branch> https://github.com/ducalex/retro-go/`


## Build everything and generate .fw:
- Generate a .fw file to be installed with odroid-go-firmware (SD Card):\
`./rg_tool.py build-fw` or `./rg_tool.py release` (clean build)
- Generate a .img to be flashed with esptool.py (Serial):\
`./rg_tool.py build-img` or `./rg_tool.py release` (clean build)

For a smaller build you can also specify which apps you want, for example the launcher + DOOM only:
1. `./rg_tool.py build-fw launcher prboom-go`


## Build, flash, and monitor individual apps for faster development:
It would be tedious to build, move to SD, and flash a full .fw all the time during development. Instead you can:
1. Flash: `./rg_tool.py --port=COM3 flash prboom-go`
2. Monitor: `./rg_tool.py --port=COM3 monitor prboom-go`
3. Flash then monitor: `./rg_tool.py --port=COM3 run prboom-go`


## Environment variables
rg_tool.py supports a few environment variables if you want to avoid passing flags all the time:
- `RG_TOOL_TARGET` represents --target
- `RG_TOOL_BAUD` represents --baud
- `RG_TOOL_PORT` represents --port


## Windows
Running `./rg_tool.py ...` on Windows might invoke the wrong Python interpreter (causing the build to fail)
or even do nothing at all. In such cases you should use `python rg_tool.py ...` instead.


## Changing the launcher's images
All images used by the launcher (headers, logos) are located in `launcher/main/images`. If you edit them you must run the `launcher/main/gen_images.py` script to regenerate `images.c`. Magenta (rgb(255, 0, 255) / 0xF81F) is used as the transparency colour.


## Capturing crash logs
When a panic occurs, Retro-Go has the ability to save debugging information to `/sd/crash.log`. This provides users with a simple way of recovering a backtrace (and often more) without having to install drivers and serial console software. A weak hook is installed into esp-idf panic's putchar, allowing us to save each chars in RTC RAM. Then, after the system resets, we can move that data to the sd card. You will find a small esp-idf patch to enable this feature in tools/patches.

To resolve the backtrace you will need the application's elf file. If lost, you can recreate it by building the app again **using the same esp-idf and retro-go versions**. Then you can run `xtensa-esp32-elf-addr2line -ifCe app-name/build/app-name.elf`.


## Porting
I don't want to maintain non-ESP32 ports in this repository but let me know if I can make small changes to make your own port easier! The absolute minimum requirements for Retro-Go are roughly:
- Processor: 200Mhz 32bit little-endian
- Memory: 2MB
- Compiler: C99 (and C++03 for handy-go)

Whilst all applications were heavily modified or even redesigned for our constrained needs, special care is taken to keep
Retro-Go and ESP32-specific code exclusively in their port file (main.c). This makes reusing them in your own codebase very easy!
80 changes: 28 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [Installation](#installation)
- [Usage](#usage)
- [Issues](#issues)
- [Theming](#theming)
- [Building](#building)
- [Acknowledgements](#acknowledgements)
- [License](#license)
Expand Down Expand Up @@ -109,6 +110,28 @@ Timezone can be configured in the launcher's options menu.
You can find the IP of your device in the *about* menu of retro-go. Then on your PC navigate to
http://192.168.x.x/ to access the file manager.

## External DAC (headphones)

Retro-Go supports [the external DAC mod for the ODROID-GO](https://github.com/backofficeshow/odroid-go-audio-hat)
which allows high quality audio through headphones. You can switch to it in the menu `Audio Out: Ext DAC`.

<details>
<summary>Pinout</summary>

| GO PIN | PCM5102A PIN |
|--------|---------|
| 1 | GND |
| 2 | - |
| 3 | LCK |
| 4 | DIN |
| 5 | BCK |
| 6 | VIN |
| 7 | - |
| 8 | - |
| 9 | - |
| 10 | - |
</details>


# Issues

Expand All @@ -126,6 +149,7 @@ lower levels that are distorted due to DAC resolution. A quick way to improve th
of the speaker wire and add a `33 Ohm (or thereabout)` resistor in series. Soldering is better but not
required, twisting the wires tightly will work just fine.
[A more involved solution can be seen here.](https://wiki.odroid.com/odroid_go/silent_volume)
Alternatively you can use the headphones DAC mod mentioned earlier in this document.

### Game Boy SRAM *(aka Save/Battery/Backup RAM)*
In Retro-Go, save states will provide you with the best and most reliable save experience. That being said, please
Expand All @@ -137,60 +161,12 @@ of losing data when powering down too quickly. Also note that when *resuming* a
to a save state if present.


# Building

## Prerequisites
You will need a working installation of [esp-idf](https://docs.espressif.com/projects/esp-idf/en/release-v4.3/esp32/get-started/index.html#get-started-get-prerequisites). Only versions 4.1 to 4.4 are supported. Support for 5.0 is coming soon.

_Note: As of retro-go 1.35, I use 4.3.3. Version 4.1.x was used for 1.20 to 1.34 versions._

### ESP-IDF Patches
Patching esp-idf may be required for full functionality. Patches are located in `tools/patches` and can be applied to your global esp-idf installation, they will not break your other projects/devices.
- `sdcard-fix`: This patch is mandatory for the ODROID-GO (and clones).
- `panic-hook`: This is to help users report bugs, see `Capturing crash logs` below for more details. The patch is optional but recommended.
- `enable-exfat`: Enable exFAT support. I don't recommended it but it works if you need it.

## Build everything and generate .fw:
- Generate a .fw file to be installed with odroid-go-firmware (SD Card):
`./rg_tool.py build-fw` or `./rg_tool.py release` (clean build)
- Generate a .img to be flashed with esptool.py (Serial):
`./rg_tool.py build-img` or `./rg_tool.py release` (clean build)
# Theming
Instructions moved to [THEMING.md](THEMING.md).

For a smaller build you can also specify which apps you want, for example the launcher + DOOM only:
1. `./rg_tool.py build-fw launcher prboom-go`

## Build, flash, and monitor individual apps for faster development:
It would be tedious to build, move to SD, and flash a full .fw all the time during development. Instead you can:
1. Flash: `./rg_tool.py --port=COM3 flash prboom-go`
2. Monitor: `./rg_tool.py --port=COM3 monitor prboom-go`
3. Flash then monitor: `./rg_tool.py --port=COM3 run prboom-go`

## Environment variables
rg_tool.py supports a few environment variables if you want to avoid passing flags all the time:
- `RG_TOOL_TARGET` represents --target
- `RG_TOOL_BAUD` represents --baud
- `RG_TOOL_PORT` represents --port

## Windows
Running `./rg_tool.py ...` on Windows might invoke the wrong Python interpreter (causing the build to fail)
or even do nothing at all. In such cases you should use `python rg_tool.py ...` instead.

## Changing the launcher's images
All images used by the launcher (headers, logos) are located in `launcher/main/images`. If you edit them you must run the `launcher/main/gen_images.py` script to regenerate `images.c`. Magenta (rgb(255, 0, 255) / 0xF81F) is used as the transparency colour.

## Capturing crash logs
When a panic occurs, Retro-Go has the ability to save debugging information to `/sd/crash.log`. This provides users with a simple way of recovering a backtrace (and often more) without having to install drivers and serial console software. A weak hook is installed into esp-idf panic's putchar, allowing us to save each chars in RTC RAM. Then, after the system resets, we can move that data to the sd card. You will find a small esp-idf patch to enable this feature in tools/patches.

To resolve the backtrace you will need the application's elf file. If lost, you can recreate it by building the app again **using the same esp-idf and retro-go versions**. Then you can run `xtensa-esp32-elf-addr2line -ifCe app-name/build/app-name.elf`.

## Porting
I don't want to maintain non-ESP32 ports in this repository but let me know if I can make small changes to make your own port easier! The absolute minimum requirements for Retro-Go are roughly:
- Processor: 200Mhz 32bit little-endian
- Memory: 2MB
- Compiler: C99 (and C++03 for handy-go)

Whilst all applications were heavily modified or even redesigned for our constrained needs, special care is taken to keep
Retro-Go and ESP32-specific code exclusively in their port file (main.c). This makes reusing them in your own codebase very easy!
# Building
Instructions moved to [CONTRIBUTING.md](CONTRIBUTING.md).


# Acknowledgements
Expand Down
70 changes: 70 additions & 0 deletions THEMING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Theming Retro-Go

This document should document what are themes, how they're structured, and how to make them.


## Theme Structure

A theme is a folder placed in `sd:/retro-go/themes` containing the following files:

| Name | Format | Description | Required |
|--|--|--|--|
| theme.json | JSON | Contains the theme metadata (description, author, colors, etc) | Yes |
| preview.png | PNG 160x120 | Theme preview to be displayed in the theme selector | No |
| background_X.png | PNG 320x240 | Launcher backgrounds where X is the name of the launcher tab (see launcher/main/images) | No |
| banner_X.png | PNG 272x24 | Launcher banners where X is the name of the launcher tab (see launcher/main/images) | No |
| logo_X.png | PNG 46x50 | Launcher logos where X is the name of the launcher tab (see launcher/main/images) | No |

### theme.json

All fields are optional (you'll have to dig in the source if you need to know the default value...).

Colors are RGB565 and can be represented as integers or hex strings. The special value `transparent` is also accepted in some places.

````json
{
"description": "Theme description",
"website": "https://example.com/retro-go-theme",
"author": "John Smith",
"dialog": {
"__comment": "This section contains global dialog colors",
"foreground": "0xFFFF",
"background": "0x0010",
"border": "0x6B4D",
"header": "0xFFFF",
"scrollbar": "0xFFFF",
"item_standard": "0xFFFF",
"item_disabled": "0x8410"
},
"launcher_1": {
"__comment": "This section contains launcher theme variant 1",
"list_standard_bg": "transparent",
"list_standard_fg": "0x8410",
"list_selected_bg": "transparent",
"list_selected_fg": "0xFFFF"
},
"launcher_2": {
"__comment": "This section contains launcher theme variant 2",
"list_standard_bg": "transparent",
"list_standard_fg": "0x8410",
"list_selected_bg": "transparent",
"list_selected_fg": "0xFFFF"
},
"launcher_3": {
"__comment": "This section contains launcher theme variant 3",
"list_standard_bg": "transparent",
"list_standard_fg": "0x8410",
"list_selected_bg": "0xFFFF",
"list_selected_fg": "0x0000"
},
"launcher_4": {
"__comment": "This section contains launcher theme variant 4",
"list_standard_bg": "transparent",
"list_standard_fg": "0xAD55",
"list_selected_bg": "0xFFFF",
"list_selected_fg": "0x0000"
}
}
````

Important: If retro-go refuses to load your theme, please run your theme.json through a JSON validator to make sure the format is correct (JSON is quite strict regarding quotes or trailing commas for example).
97 changes: 65 additions & 32 deletions components/retro-go/rg_gui.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ static struct
rg_color_t item_disabled;
rg_color_t scrollbar;
} style;
char theme[32];
char theme_name[32];
cJSON *theme_obj;
bool initialized;
} gui;

Expand All @@ -45,62 +46,94 @@ void rg_gui_init(void)
gui.initialized = true;
}

static int get_theme_value(cJSON *theme, const char *key, int default_value)
{
cJSON *obj = cJSON_GetObjectItem(theme, key);
if (obj && cJSON_IsNumber(obj))
return obj->valueint;
// TODO: We must parse stringy hex values!
return default_value;
}

bool rg_gui_set_theme(const char *theme_name)
{
char theme_path[RG_PATH_MAX];
cJSON *theme = NULL;
char pathbuf[RG_PATH_MAX];
cJSON *new_theme = NULL;

// Cleanup the current theme
cJSON_Delete(gui.theme_obj);
gui.theme_obj = NULL;

if (theme_name && theme_name[0])
{
snprintf(theme_path, RG_PATH_MAX, "%s/%s/theme.json", RG_BASE_PATH_THEMES, theme_name);
FILE *fp = fopen(theme_path, "rb");
snprintf(pathbuf, RG_PATH_MAX, "%s/%s/theme.json", RG_BASE_PATH_THEMES, theme_name);
FILE *fp = fopen(pathbuf, "rb");
if (fp)
{
fseek(fp, 0, SEEK_END);
long length = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = calloc(1, length + 1);
if (fread(buffer, 1, length, fp))
theme = cJSON_Parse(buffer);
new_theme = cJSON_Parse(buffer);
free(buffer);
fclose(fp);
if (!new_theme)
RG_LOGE("Failed to load theme JSON from '%s'!\n", pathbuf);
}
if (!theme)
RG_LOGE("Failed to load theme '%s'!\n", theme_name);
}

gui.style.box_background = get_theme_value(theme, "box_background", C_NAVY);
gui.style.box_header = get_theme_value(theme, "box_header", C_WHITE);
gui.style.box_border = get_theme_value(theme, "box_border", C_DIM_GRAY);
gui.style.item_standard = get_theme_value(theme, "item_standard", C_WHITE);
gui.style.item_disabled = get_theme_value(theme, "item_disabled", C_GRAY);
gui.style.scrollbar = get_theme_value(theme, "scrollbar", C_WHITE);

RG_LOGI("Theme set to '%s'!\n", theme_name ?: "(none)");

rg_settings_set_string(NS_GLOBAL, SETTING_THEME, theme_name);
strcpy(gui.theme, theme_name ?: "");
if (new_theme)
{
rg_settings_set_string(NS_GLOBAL, SETTING_THEME, theme_name);
strcpy(gui.theme_name, theme_name);
// FIXME: Keeping the theme around uses quite a lot of internal memory (about 3KB)...
// We should probably convert it to a regular array or hashmap.
gui.theme_obj = new_theme;
RG_LOGI("Theme set to '%s'!\n", theme_name);
}
else
{
rg_settings_set_string(NS_GLOBAL, SETTING_THEME, NULL);
strcpy(gui.theme_name, "");
gui.theme_obj = NULL;
RG_LOGI("Using built-in theme!\n");
}

cJSON_Delete(theme);
gui.style.box_background = rg_gui_get_theme_color("dialog", "background", C_NAVY);
gui.style.box_header = rg_gui_get_theme_color("dialog", "header", C_WHITE);
gui.style.box_border = rg_gui_get_theme_color("dialog", "border", C_DIM_GRAY);
gui.style.item_standard = rg_gui_get_theme_color("dialog", "item_standard", C_WHITE);
gui.style.item_disabled = rg_gui_get_theme_color("dialog", "item_disabled", C_GRAY);
gui.style.scrollbar = rg_gui_get_theme_color("dialog", "scrollbar", C_WHITE);

if (gui.initialized)
rg_system_event(RG_EVENT_REDRAW, NULL);

return true;
}

const char *rg_gui_get_theme(void)
rg_color_t rg_gui_get_theme_color(const char *section, const char *key, rg_color_t default_value)
{
cJSON *root = section ? cJSON_GetObjectItem(gui.theme_obj, section) : gui.theme_obj;
cJSON *obj = cJSON_GetObjectItem(root, key);
if (cJSON_IsNumber(obj))
return obj->valueint;
char *strval = cJSON_GetStringValue(obj);
if (!strval || strlen(strval) < 4)
return default_value;
if (strcmp(strval, "transparent") == 0)
return C_TRANSPARENT;
int intval = (int)strtol(strval, NULL, 0);
// It is better to specify colors as RGB565 to avoid data loss, but we also accept RGB888 for convenience
if (strlen(strval) == 8 && strval[0] == '0' && strval[1] == 'x')
return (((intval >> 19) & 0x1F) << 11) | (((intval >> 10) & 0x3F) << 5) | (((intval >> 3) & 0x1F));
return intval;
}

rg_image_t *rg_gui_get_theme_image(const char *name)
{
char pathbuf[RG_PATH_MAX];
if (!name || !rg_gui_get_theme_name())
return NULL;
snprintf(pathbuf, RG_PATH_MAX, "%s/%s/%s", RG_BASE_PATH_THEMES, rg_gui_get_theme_name(), name);
return rg_image_load_from_file(pathbuf, 0);
}

const char *rg_gui_get_theme_name(void)
{
return strlen(gui.theme) ? gui.theme : NULL;
return gui.theme_name[0] ? gui.theme_name : NULL;
}

void rg_gui_set_buffered(bool buffered)
Expand Down Expand Up @@ -1053,7 +1086,7 @@ static rg_gui_event_t theme_cb(rg_gui_option_t *option, rg_gui_event_t event)
free(path);
}

strcpy(option->value, rg_gui_get_theme() ?: "Default");
strcpy(option->value, rg_gui_get_theme_name() ?: "Default");
return RG_DIALOG_VOID;
}

Expand Down
Loading

0 comments on commit d53c9e9

Please sign in to comment.