diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..8cc54f745 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,28 @@ +name: SonarCloud +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + env: + BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Install sonar-scanner and build-wrapper + uses: SonarSource/sonarcloud-github-c-cpp@v1 + - name: Run build-wrapper + run: | + build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} .\fbt updater_package + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/applications/plugins/tama_p1/README.md b/applications/plugins/tama_p1/README.md index 20690c7a2..2e95d1e76 100644 --- a/applications/plugins/tama_p1/README.md +++ b/applications/plugins/tama_p1/README.md @@ -3,15 +3,26 @@ Tama P1 Emulator for Flipper Zero This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://github.com/jcrona/tamalib/). +![Alt Text](tama.gif) + How to play ----------- Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`. +Use a search engine to find the Tamagotchi ROM. There is a file named `a`. +Rename this to `rom.bin`. + Left button is A, OK is B, and right button is C. Hold the back button to exit. There is currently no saving, so your progress will be reset when you exit the app. Building -------- +Move this folder into flippers applications/plugins/tama_p1. + + +Launching the app, directly from console to flipper: +`./fbt launch_app APPSRC=applications\plugins\tama_p1` + Run the following to compile icons: ``` scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled @@ -20,16 +31,25 @@ scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in `site_cons/cc.scons` to suppress unused parameter errors in TamaLIB. +Debugging +--------- +Using the serial script from [FlipperScripts](https://github.com/DroomOne/FlipperScripts/blob/main/serial_logger.py) +it is easy to add direct logging after running the appliation: +`python .\serial_logger.py` + +`./fbt launch_app APPSRC=applications\plugins\tama_p1; python .\serial_logger.py` + + Implemented ----------- - Basic emulation - Input - Sound +- Saving/Loading emaulator state (stored in `/ext/tama_p1/save.bin`) To-do ----- -- Saving/loading - - Multiple slots? +- Slots - In-game reset - Test mode? - Volume adjustment diff --git a/applications/plugins/tama_p1/hal.c b/applications/plugins/tama_p1/hal.c index 7c07c5252..211457803 100644 --- a/applications/plugins/tama_p1/hal.c +++ b/applications/plugins/tama_p1/hal.c @@ -36,11 +36,11 @@ static bool_t tama_p1_hal_is_log_enabled(log_level_t level) { static void tama_p1_hal_log(log_level_t level, char* buff, ...) { if(!tama_p1_hal_is_log_enabled(level)) return; - FuriString* string = NULL; + FuriString* string = furi_string_alloc(); va_list args; va_start(args, buff); furi_string_cat_vprintf(string, buff, args); - va_end(args); + va_end(args); switch(level) { case LOG_ERROR: @@ -50,7 +50,10 @@ static void tama_p1_hal_log(log_level_t level, char* buff, ...) { FURI_LOG_I(TAG_HAL, "%s", furi_string_get_cstr(string)); break; case LOG_MEMORY: + break; case LOG_CPU: + FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string)); + break; default: FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string)); break; diff --git a/applications/plugins/tama_p1/tama.h b/applications/plugins/tama_p1/tama.h index d3b67b90d..e2a267443 100644 --- a/applications/plugins/tama_p1/tama.h +++ b/applications/plugins/tama_p1/tama.h @@ -9,6 +9,11 @@ #define TAMA_LCD_ICON_SIZE 14 #define TAMA_LCD_ICON_MARGIN 1 +#define STATE_FILE_MAGIC "TLST" +#define STATE_FILE_VERSION 2 +#define TAMA_SAVE_PATH EXT_PATH("tama_p1/save.bin") + + typedef struct { FuriThread* thread; hal_t hal; diff --git a/applications/plugins/tama_p1/tama_p1.c b/applications/plugins/tama_p1/tama_p1.c index 6e7972000..7e627adc9 100644 --- a/applications/plugins/tama_p1/tama_p1.c +++ b/applications/plugins/tama_p1/tama_p1.c @@ -38,26 +38,10 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) { // FURI_LOG_D(TAG, "Drawing frame"); // Calculate positioning uint16_t canv_width = canvas_width(canvas); - uint16_t canv_height = canvas_height(canvas); uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR; - uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR; - uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2; + uint16_t lcd_matrix_top = 0; uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2; - uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN; - uint16_t lcd_icon_upper_left = lcd_matrix_left; - uint16_t lcd_icon_lower_top = - lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN; - uint16_t lcd_icon_lower_left = lcd_matrix_left; - uint16_t lcd_icon_spacing_horiz = - (lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE; - - // Draw pixels - // canvas_draw_frame( - // canvas, - // lcd_matrix_left, - // lcd_matrix_top, - // lcd_matrix_scaled_width, - // lcd_matrix_scaled_height); + uint16_t y = lcd_matrix_top; for(uint8_t row = 0; row < 16; ++row) { @@ -74,30 +58,20 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) { y += TAMA_SCREEN_SCALE_FACTOR; } - // Draw icons + // Draw Icons on bottom uint8_t lcd_icons = g_ctx->icons; - // Top - y = lcd_icon_upper_top; - uint16_t x_ic = lcd_icon_upper_left; - for(uint8_t i = 0; i < 4; ++i) { - // canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE); + uint16_t x_ic = 0; + y = 64 - TAMA_LCD_ICON_SIZE; + for(uint8_t i = 0; i < 7; ++i) { if(lcd_icons & 1) { canvas_draw_icon(canvas, x_ic, y, icons_list[i]); } - x_ic += lcd_icon_spacing_horiz; + x_ic += TAMA_LCD_ICON_SIZE + 4; lcd_icons >>= 1; } - // Bottom - y = lcd_icon_lower_top; - x_ic = lcd_icon_lower_left; - for(uint8_t i = 4; i < 8; ++i) { - // canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE); - if(lcd_icons & 1) { - canvas_draw_icon(canvas, x_ic, y, icons_list[i]); - } - x_ic += lcd_icon_spacing_horiz; - lcd_icons >>= 1; + if (lcd_icons & 7) { + canvas_draw_icon(canvas, 128 - TAMA_LCD_ICON_SIZE, 0, icons_list[7]); } } @@ -118,6 +92,228 @@ static void tama_p1_update_timer_callback(FuriMessageQueue* event_queue) { furi_message_queue_put(event_queue, &event, 0); } + + +static void tama_p1_load_state() { + state_t *state; + uint8_t buf[4]; + bool error = false; + state = tamalib_get_state(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + + storage_file_read(file, &buf, 4); + if (buf[0] != (uint8_t) STATE_FILE_MAGIC[0] || buf[1] != (uint8_t) STATE_FILE_MAGIC[1] || + buf[2] != (uint8_t) STATE_FILE_MAGIC[2] || buf[3] != (uint8_t) STATE_FILE_MAGIC[3]) { + FURI_LOG_E(TAG, "FATAL: Wrong state file magic in \"%s\" !\n", TAMA_SAVE_PATH); + error = true; + } + + storage_file_read(file, &buf, 1); + if (buf[0] != STATE_FILE_VERSION) { + FURI_LOG_E(TAG, "FATAL: Unsupported version"); + error = true; + } + if (!error) { + FURI_LOG_D(TAG, "Reading save.bin"); + + storage_file_read(file, &buf, 2); + *(state->pc) = buf[0] | ((buf[1] & 0x1F) << 8); + + storage_file_read(file, &buf, 2); + *(state->x) = buf[0] | ((buf[1] & 0xF) << 8); + + storage_file_read(file, &buf, 2); + *(state->y) = buf[0] | ((buf[1] & 0xF) << 8); + + storage_file_read(file, &buf, 1); + *(state->a) = buf[0] & 0xF; + + storage_file_read(file, &buf, 1); + *(state->b) = buf[0] & 0xF; + + storage_file_read(file, &buf, 1); + *(state->np) = buf[0] & 0x1F; + + storage_file_read(file, &buf, 1); + *(state->sp) = buf[0]; + + storage_file_read(file, &buf, 1); + *(state->flags) = buf[0] & 0xF; + + storage_file_read(file, &buf, 4); + *(state->tick_counter) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + storage_file_read(file, &buf, 4); + *(state->clk_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + storage_file_read(file, &buf, 4); + *(state->prog_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + storage_file_read(file, &buf, 1); + *(state->prog_timer_enabled) = buf[0] & 0x1; + + storage_file_read(file, &buf, 1); + *(state->prog_timer_data) = buf[0]; + + storage_file_read(file, &buf, 1); + *(state->prog_timer_rld) = buf[0]; + + storage_file_read(file, &buf, 4); + *(state->call_depth) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + + FURI_LOG_D(TAG, "Restoring Interupts"); + for (uint32_t i = 0; i < INT_SLOT_NUM; i++) { + storage_file_read(file, &buf, 1); + state->interrupts[i].factor_flag_reg = buf[0] & 0xF; + + storage_file_read(file, &buf, 1); + state->interrupts[i].mask_reg = buf[0] & 0xF; + + storage_file_read(file, &buf, 1); + state->interrupts[i].triggered = buf[0] & 0x1; + } + + /* First 640 half bytes correspond to the RAM */ + FURI_LOG_D(TAG, "Restoring RAM"); + for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) { + storage_file_read(file, &buf, 1); + SET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR, buf[0] & 0xF); + } + + /* I/Os are from 0xF00 to 0xF7F */ + FURI_LOG_D(TAG, "Restoring I/O"); + for (uint32_t i = 0; i < MEM_IO_SIZE; i++) { + storage_file_read(file, &buf, 1); + SET_IO_MEMORY(state->memory, i + MEM_IO_ADDR, buf[0] & 0xF); + } + FURI_LOG_D(TAG, "Refreshing Hardware"); + tamalib_refresh_hw(); + } + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + + +static void tama_p1_save_state() { + + // Saving state + FURI_LOG_D(TAG, "Saving Gamestate"); + + uint8_t buf[4]; + state_t *state; + uint32_t offset = 0; + state = tamalib_get_state(); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + buf[0] = (uint8_t) STATE_FILE_MAGIC[0]; + buf[1] = (uint8_t) STATE_FILE_MAGIC[1]; + buf[2] = (uint8_t) STATE_FILE_MAGIC[2]; + buf[3] = (uint8_t) STATE_FILE_MAGIC[3]; + offset += storage_file_write(file, &buf, sizeof(buf)); + + buf[0] = STATE_FILE_VERSION & 0xFF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->pc) & 0xFF; + buf[1] = (*(state->pc) >> 8) & 0x1F; + offset += storage_file_write(file, &buf, 2); + + buf[0] = *(state->x) & 0xFF; + buf[1] = (*(state->x) >> 8) & 0xF; + offset += storage_file_write(file, &buf, 2); + + buf[0] = *(state->y) & 0xFF; + buf[1] = (*(state->y) >> 8) & 0xF; + offset += storage_file_write(file, &buf, 2); + + buf[0] = *(state->a) & 0xF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->b) & 0xF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->np) & 0x1F; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->sp) & 0xFF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->flags) & 0xF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->tick_counter) & 0xFF; + buf[1] = (*(state->tick_counter) >> 8) & 0xFF; + buf[2] = (*(state->tick_counter) >> 16) & 0xFF; + buf[3] = (*(state->tick_counter) >> 24) & 0xFF; + offset += storage_file_write(file, &buf, sizeof(buf)); + + buf[0] = *(state->clk_timer_timestamp) & 0xFF; + buf[1] = (*(state->clk_timer_timestamp) >> 8) & 0xFF; + buf[2] = (*(state->clk_timer_timestamp) >> 16) & 0xFF; + buf[3] = (*(state->clk_timer_timestamp) >> 24) & 0xFF; + offset += storage_file_write(file, &buf, sizeof(buf)); + + buf[0] = *(state->prog_timer_timestamp) & 0xFF; + buf[1] = (*(state->prog_timer_timestamp) >> 8) & 0xFF; + buf[2] = (*(state->prog_timer_timestamp) >> 16) & 0xFF; + buf[3] = (*(state->prog_timer_timestamp) >> 24) & 0xFF; + offset += storage_file_write(file, &buf, sizeof(buf)); + + buf[0] = *(state->prog_timer_enabled) & 0x1; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->prog_timer_data) & 0xFF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->prog_timer_rld) & 0xFF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = *(state->call_depth) & 0xFF; + buf[1] = (*(state->call_depth) >> 8) & 0xFF; + buf[2] = (*(state->call_depth) >> 16) & 0xFF; + buf[3] = (*(state->call_depth) >> 24) & 0xFF; + offset += storage_file_write(file, &buf, sizeof(buf)); + + for (uint32_t i = 0; i < INT_SLOT_NUM; i++) { + buf[0] = state->interrupts[i].factor_flag_reg & 0xF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = state->interrupts[i].mask_reg & 0xF; + offset += storage_file_write(file, &buf, 1); + + buf[0] = state->interrupts[i].triggered & 0x1; + offset += storage_file_write(file, &buf, 1); + } + + /* First 640 half bytes correspond to the RAM */ + for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) { + buf[0] = GET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR) & 0xF; + offset += storage_file_write(file, &buf, 1); + } + + /* I/Os are from 0xF00 to 0xF7F */ + for (uint32_t i = 0; i < MEM_IO_SIZE; i++) { + buf[0] = GET_IO_MEMORY(state->memory, i + MEM_IO_ADDR) & 0xF; + offset += storage_file_write(file, &buf, 1); + } + } + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + FURI_LOG_D(TAG, "Finished Writing %lu", offset); +} + + static int32_t tama_p1_worker(void* context) { bool running = true; FuriMutex* mutex = context; @@ -125,6 +321,9 @@ static int32_t tama_p1_worker(void* context) { cpu_sync_ref_timestamp(); LL_TIM_EnableCounter(TIM2); + + tama_p1_load_state(); + while(running) { if(furi_thread_flags_get()) { running = false; @@ -138,6 +337,8 @@ static int32_t tama_p1_worker(void* context) { furi_mutex_release(mutex); return 0; } + + static void tama_p1_init(TamaApp* const ctx) { g_ctx = ctx; @@ -270,6 +471,8 @@ int32_t tama_p1_app(void* p) { if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) { furi_timer_stop(timer); running = false; + + tama_p1_save_state(); } } diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..43c6c8dec --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=ClaraCrazy_Flipper-Xtreme +sonar.organization=claracrazy + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=Flipper-Xtreme +#sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8