From 5786cd9479a44098301abafe6265f6ec5bca514a Mon Sep 17 00:00:00 2001 From: Cheru Berhanu Date: Fri, 19 Jul 2024 17:32:43 -0400 Subject: [PATCH] temp --- firmware/spade/docker/dockerfile | 2 +- firmware/spade/src/CMakeLists.txt | 2 +- firmware/spade/src/rpi/CMakeLists.txt | 37 +- .../spade/src/rpi/jerry/lib/libjerry-core.a | Bin 873568 -> 865800 bytes .../spade/src/rpi/jerry/lib/libjerry-ext.a | Bin 39566 -> 38846 bytes .../src/rpi/jerry/lib/libjerry-port-default.a | Bin 9664 -> 9372 bytes firmware/spade/src/rpi/jerry/refresh.sh | 11 +- firmware/spade/src/rpi/main.c | 245 +++++++--- firmware/spade/src/rpi/upload.h | 455 ++++++++++++++++-- firmware/spade/src/version.json | 2 +- .../kicad/sprig_console.kicad_prl | 10 +- .../kicad/sprig_console.kicad_pro | 103 +++- scripts/gardenshed/gdb-server.sh | 2 +- src/components/navbar-editor.tsx | 8 +- src/lib/upload.ts | 14 +- 15 files changed, 749 insertions(+), 142 deletions(-) diff --git a/firmware/spade/docker/dockerfile b/firmware/spade/docker/dockerfile index 11f63e3dbe..891a9e1829 100644 --- a/firmware/spade/docker/dockerfile +++ b/firmware/spade/docker/dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.19 -RUN apk add git python3 clang make cmake entr uglify-js gcc-arm-none-eabi g++-arm-none-eabi gdb-multiarch +RUN apk add git python3 clang make cmake entr uglify-js gcc-arm-none-eabi g++-arm-none-eabi gdb-multiarch gcc COPY ./importBuildRepos.sh /opt/importBuildRepos.sh diff --git a/firmware/spade/src/CMakeLists.txt b/firmware/spade/src/CMakeLists.txt index 07b307e59a..cd9f070945 100644 --- a/firmware/spade/src/CMakeLists.txt +++ b/firmware/spade/src/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_BUILD_TYPE Release) +set(CMAKE_BUILD_TYPE Debug) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-void-pointer-to-int-cast -Wno-int-to-void-pointer-cast -Wno-pointer-sign -Werror=implicit-function-declaration") add_executable(spade "${SPADE_TARGET}/main.c") diff --git a/firmware/spade/src/rpi/CMakeLists.txt b/firmware/spade/src/rpi/CMakeLists.txt index 8ab9ccce77..4d3fb315bd 100644 --- a/firmware/spade/src/rpi/CMakeLists.txt +++ b/firmware/spade/src/rpi/CMakeLists.txt @@ -1,30 +1,29 @@ pico_sdk_init() -add_definitions(-DSPADE_EMBEDDED -DSPADE_AUDIO -DPICO_NO_BI_PROGRAM_BUILD_DATE) -set(DCMAKE_BUILD_TYPE Release) +add_definitions(-DSPADE_EMBEDDED -DSPADE_AUDIO -DPICO_NO_BI_PROGRAM_BUILD_DATE -DPICO_USE_STACK_GUARDS) +set(DCMAKE_BUILD_TYPE Debug) target_compile_definitions(spade PRIVATE - # compile time configuration of I2S - PICO_AUDIO_I2S_MONO_INPUT=1 - USE_AUDIO_I2S=1 - PICO_AUDIO_I2S_DATA_PIN=9 - PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 - # PICO_DEFAULT_UART=0 - # PICO_DEFAULT_UART_TX_PIN=28 - # PICO_DEFAULT_UART_RX_PIN=29 + # compile time configuration of I2S + PICO_AUDIO_I2S_MONO_INPUT=1 + USE_AUDIO_I2S=1 + PICO_AUDIO_I2S_DATA_PIN=9 + PICO_AUDIO_I2S_CLOCK_PIN_BASE=10 + # PICO_DEFAULT_UART=0 + # PICO_DEFAULT_UART_TX_PIN=28 + # PICO_DEFAULT_UART_RX_PIN=29 ) target_link_libraries(spade PRIVATE - pico_stdlib - pico_audio_i2s - pico_multicore - hardware_spi - hardware_timer - hardware_pwm - hardware_adc + pico_stdlib + pico_audio_i2s + pico_multicore + hardware_spi + hardware_timer + hardware_pwm + hardware_adc ) pico_enable_stdio_usb(spade 1) pico_enable_stdio_uart(spade 0) # create map/bin/hex file etc. -pico_add_extra_outputs(spade) - +pico_add_extra_outputs(spade) \ No newline at end of file diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-core.a b/firmware/spade/src/rpi/jerry/lib/libjerry-core.a index ef160b9e45d072c5ab199b956ffc93370f8d8129..f265b60d915f0acf4af8ffc4ff9c92793fb17551 100644 GIT binary patch delta 38298 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 delta 45754 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 LnWkKC4IceJ)ix8j diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a b/firmware/spade/src/rpi/jerry/lib/libjerry-ext.a index 4d304f8849fec39cf59f41e1792291517caae385..7bb75d99c2beb7743157aaeb98b4593c3b9fa9b5 100644 GIT binary patch delta 2572 zcmc&#T})eL82-MGo{qv`hqgML2uGT5+702HQp%6CSu;Xw`RVvk7@P7J3@Z#68(RLP zA?jE_hkLy!%s3K_yCDJhX|gQFRo#Ub%d(3lGchK+FmW*$f8vcX6ThDB5F#x{6B1AI zoYR~peV+Gu-tXz#pOVj4$-;qDyX>$#WJ!J}zhb%)A@l!;>D?lCXCkx}HD4n1+;0fg zA3&(tj?fG32pyZ*hH2$x*F}W>3xd}XQgNmTp;wIvO)VwH8|?l0kN$~Z@5snlsG|%1 zD9on6;O{e@Rznd4Nr+wXyDUjgheHr#sT@xC*zgME@k6G8;gf^CLfy&1(U47$ON!(o z31rU{^=GYP;v=v(Q~)oTT5t}0+ixKsB3P>_z;|H7By!6bp)2sQ#{_OOk2@h?wpp~O zosM$IYY@qOk=`_amzIRy1wRco(x9-Mg9S);eZbgPT^3?WSJ)53pDs}^>__Nf8m!gl zbF(5sZHka?w^3tyyX=H725dM=Z@8T#89zPc8Pn+_971;x@UDEwteK39!2PgVBjO0$ zV|O2H)QDW4UZrTPBF8hs9h-(% z!?fR~F>M*M`?S}FUx)Ozyd-@6(CIVM+qHcvXUi5&!ei$7<4VF+P~7+gtk?7ZcE%kz zDIb_g#J6q6;zq3*o#l?$jK$T=9d>aJPfui%H)tcB+#X%y{ej{ z%JdnN>{o^v_QU`e>YwD+*{3Ykeew{2)R+w?TKL_FfQFwRR|RafAM-HODq^?d{95a- zaK++=+qHfw9m`F`xjk~-aV=uIBq^PeLpzRN#z_`!f?j*ENQbN7hpqs=31!{QY{}=k zE#z)&#|kild)*?tZ*+^?Eq3(k<7W409%*~L&`!`xJzUZw4U$iS_dG`0{<4n4ebCWA z!RT43;mX*FHuMpMqHKjyA<&wKtq>Rx3BL!9n)kznfh>)X?Q(qnFx+rIcJ*7=L8}<4+(x^vDDfRMy@QVM`umP+Z{Jm>=z%D}M_sZah1dmA`Yz$xhi0xeZqG zt`4GZGhG=oBsN6$xaczzy(#*;DG2q&4Y~fBYDlwUNJf~6ILu&RveIHFQ6FVdnb?=C z`m1zm_x?DowI^~#xl;WO>J8T>6EsB|W+FO{X9;}+`$87TJg5Hog)s=NBCMVh^>s|| zSBe7ifV((rHEuTqnnvs3&Dn#RQ3TJgVMq&EAvl)}@AvcUSs33;G7=AEnN1#Lh=P&q%2xU WI7Jy=ToT!fz>>&Sr?bdnGX4g6a)I^$ delta 3310 zcmc(hZ%i9y9LJyMp;s9=h0+i+Cme0UwGNxRYbgcW(n_GvIra|+W5%_@Oo6i2!eC@z ziID|lkZwPPWhVQRy%vEbHV-})`S>oULiZ>HqXtEe1i*M!&BtCcL8Yhbv)0#E; z^>Wwt?$ht{eZIfnts7+iBa+%tC^%iBtIp1!HPt6yL&*3DE19o{dtXK9?-lSO6kI@P z&o2mdbR*RH0YXQ}mP#rYd+3+{OGW1qQtN~bp|J*plJr3jtLbut&IJEi{l^lCv6Dx` z(h6SRUZpMw%hvJ&%d#R*-&ld?oel>*3L^NuR=fh0B{6qmkPWoI#CF7D(Vj@S?>H;i zZK5r4EGn>;7f$p=d)aVe@Hu+1&T7qkCeZtdc6zsZxKiSQHD8ldQTi*!!%$py3OB>i z5i|K3!K9~}Sw{$c2RBB{%vTtpYfx2M25-!ikTV2QGv&}=G?+A4d8Z4G8!e>JBAqwh zDasA^W^_&}XD{#6x0(=&Fc5Dx!4K|n%KT)D8M`6r;cx`5(f3yP*26Jr4xugu`U$T= zLse1aWmU;tJ;Xkj`|UwGn$x` zUIqQE#lZf%q;>TW8hRFg2$g|nawYUgbAg+fvNg7u$th*c@e<6oag2+uw?kcTCQ8qm zm9yX5w1ljy7a=@k#c9|ZtjrmZ+EW1EeuK0TjHsX!2>3-##w2b`^(>b?$wab@JN9TX zx_Dk;{5Y?S@J+xaz0&U5%E`DNkTae|4erxItrJ}`YM1;Q`eGc)!GgA-LWBZMO%1HY zpN9+(B;S6o3O=Sf;mZRY^Avq>Y1RqWEV_756aD{jg16%#PEZuKWik7+nB9jS4JK>^ zgHpP)Le*5uv~M2P;uLv@lKZXF>fuW`A3Mu0J^r#yM=9SW9qBN45jlh75+S9RO}ky$BbFOt~++tmJ{=6o!#$ z3lxoX&?T2UK4kX%GBnO`%p~1D-J6@q0)|n8c`MNaBZ;$CWFcnc@TXF2v?50Vj8;`NWK;*BffZC|2su;J=&bA^O0f_v|_<1T1ixP(Kny0D9SxOmY_t`{h=wE~@s@(7q*OX2_k diff --git a/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a b/firmware/spade/src/rpi/jerry/lib/libjerry-port-default.a index f0a1789bd2a9d6e6570d3bdf84b9e27ffa22d54c..dfe455fd3b6190155b86cdd5bcad55bd29ae6e5c 100644 GIT binary patch delta 994 zcmaiyPiPZC6vpRmy6&19Ge9J$S3Mg&C$n3L!ZrhMnA3c}?|t9fQ?oVJxC}p&M+?z9&bsS1L z&)LZI0cgR|zzuU^a=dhvo*pkv-AYoPN%J(rPhH3GiBYUoT23fX!H;|HKo3qwO{5V6 zo*|D|u!@f~OZf`8tR>VXg^(fSv;%)@VY<6~&cU=k2xT1Bdws+UwGDj%Jjv%l1I|jx zGJXqMssV%y;5#i2dE~9hW@Z_4q}dT%XwCTOxn^hftpO0czQ57zSY5eoh7htTMieyY?~g;_*zVr*S%ydSBz(Z1p_C6yp*Ckc1enBBdn3qY2~rgRuRIW4 zf)oV^lOSdBazhw3!_C%W=dyAw6Zpow=0oDH<#WfP0-K@(7A|$3Yv{qO3}pp}MH}*X zFJh@Q;1V3L`B7Os?-GUcdcD)d{~DRM-H^s=y$7ej2d98@#&JBe*QkwjR|8@}Kl+|Z zoVfA=u%soFHNY>?gjx|8-(^D#Owpzd$#@O9g!A-4Q|;~8O+{T1nHO=ZFOJnicU=Zp auCGH`CwSp-Lj5JOcrR1Y1y51zjd&OSLCLy81|Es@2$l*oD-#%HB20JHEdYoTemG72x8o#t$EX~o`@0p=VrzEqr-w1tt)JG5CzRUJaU9?A7ExiI6Jp`D5}A z{*=c(PuQ?oEtZ(g?Hk57ftmIneIg zAIf`9jdnzLcuSJbxbXdnM{VDymd|%U2wCDfH{VtK;6E+T@s`_SIwxK2>BRk$fo?zE z%9z5uUeEV0gH!ZaU5$KqK?VOtUx}d%VKb&mUjf(r!#=`^HI6>c(O!YBaq3SI*g}1v wdtlSXuHx%~r_Qa9=|>6v;6&_@6@H1nVa` diff --git a/firmware/spade/src/rpi/jerry/refresh.sh b/firmware/spade/src/rpi/jerry/refresh.sh index b9b8f3ab9d..6fd77cab31 100755 --- a/firmware/spade/src/rpi/jerry/refresh.sh +++ b/firmware/spade/src/rpi/jerry/refresh.sh @@ -1,11 +1,12 @@ cd ~/jerryscript_build rm -rf example-* -# CMAKE_ASM_COMPILER=arm-none-eabi-gcc -# CMAKE_C_COMPILER=arm-none-eabi-gcc -# CMAKE_CXX_COMPILER=arm-none-eabi-g++ -# CMAKE_LINKER=arm-none-eabi-ld -# CMAKE_OBJCOPY=arm-none-eabi-objcopy +export CC=arm-none-eabi-gcc +export CMAKE_ASM_COMPILER=arm-none-eabi-gcc +export CMAKE_C_COMPILER=arm-none-eabi-gcc +export CMAKE_CXX_COMPILER=arm-none-eabi-g++ +export CMAKE_LINKER=arm-none-eabi-ld +export CMAKE_OBJCOPY=arm-none-eabi-objcopy # --debug \ python3 jerryscript/tools/build.py \ diff --git a/firmware/spade/src/rpi/main.c b/firmware/spade/src/rpi/main.c index fb4692b088..fe226cf383 100644 --- a/firmware/spade/src/rpi/main.c +++ b/firmware/spade/src/rpi/main.c @@ -72,6 +72,7 @@ typedef struct { uint8_t last_state; uint8_t ring_i; } ButtonState; +// W, S, A, D, I, K, J, L uint button_pins[] = { 5, 7, 6, 8, 12, 14, 13, 15 }; static ButtonState button_states[ARR_LEN(button_pins)] = {0}; @@ -248,6 +249,25 @@ static int load_new_scripts(void) { } #endif +typedef enum { + NEW_SLOT, + RUN_GAME, + DELETE_CONFIRM + } Welcome_Screen; + + int count_digits(uint32_t number) { + if (number < 10) return 1; + if (number < 100) return 2; + if (number < 1000) return 3; + if (number < 10000) return 4; + if (number < 100000) return 5; + if (number < 1000000) return 6; + if (number < 10000000) return 7; + if (number < 100000000) return 8; + if (number < 1000000000) return 9; + return 10; + } + int main() { timer_hw->dbgpause = 0; @@ -265,74 +285,173 @@ int main() { jerry_init(JERRY_INIT_MEM_STATS); init(sprite_free_jerry_object); // TODO: document - while(!save_read()) { - // No game stored in memory - strcpy(errorbuf, " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " PLEASE UPLOAD \n" - " A GAME \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " sprig.hackclub.com \n"); - render_errorbuf(); - st7735_fill_start(); - render(st7735_fill_send); - st7735_fill_finish(); + // Start a core to listen for keypresses. + multicore_reset_core1(); - load_new_scripts(); - } + // update_save_version(); // init here to avoid irqs on other core - // Start a core to listen for keypresses. - multicore_launch_core1(core1_entry); + multicore_launch_core1(core1_entry); - /** - * We get a bunch of fake keypresses at startup, so we need to - * drain them from the FIFO queue. - * - * What really needs to be done here is to have button_init - * record when it starts so that we can ignore keypresses after - * that timestamp. - */ - sleep_ms(50); - while (multicore_fifo_rvalid()) multicore_fifo_pop_blocking(); + /** + * We get a bunch of fake keypresses at startup, so we need to + * drain them from the FIFO queue. + * + * What really needs to be done here is to have button_init + * record when it starts so that we can ignore keypresses after + * that timestamp. + */ + sleep_ms(50); + while (multicore_fifo_rvalid()) multicore_fifo_pop_blocking(); - /** - * Wait for a keypress to start the game. - * - * This is important so games with e.g. infinite loops don't - * brick the device as soon as they start up. - */ - while(!multicore_fifo_rvalid()) { - strcpy(errorbuf, " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " PRESS ANY BUTTON \n" - " TO RUN \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " sprig.hackclub.com \n"); - render_errorbuf(); - st7735_fill_start(); - render(st7735_fill_send); - st7735_fill_finish(); + int games_i = 0; - load_new_scripts(); - } + int games_len = 8; + Game* games = malloc(games_len * sizeof(Game)); + + Welcome_Screen welcome_state = NEW_SLOT; + + for (;;) { + + if (games_i >= games_len && games_i != 0) { + games_i = games_len - 1; + } + + if (welcome_state == DELETE_CONFIRM) { + // no-op + } else if (games_len == 0) { + welcome_state = NEW_SLOT; + } else { + welcome_state = RUN_GAME; + set_game(games[games_i]); + } + + /** + * Wait for a keypress to start the game. + * This is important so games with e.g. infinite loops don't + * brick the device as soon as they start up. + */ + + if (multicore_fifo_rvalid()) { + uint32_t c = multicore_fifo_pop_blocking(); + + if (welcome_state == DELETE_CONFIRM) { + if (c == 7) { // S + welcome_state = RUN_GAME; + } else if (c == 5) { // W + delete_game(games[games_i]); + welcome_state = RUN_GAME; + continue; + } + } else if (c == 6) { // A + if (games_i > 0) games_i--; + } else if (c == 8) { // D + if (games_i < games_len - 1) games_i++; + } else if (c == 7 && welcome_state == RUN_GAME) { // S + welcome_state = DELETE_CONFIRM; + } else if (welcome_state == RUN_GAME && c == 5) { // other button + break; + } + } + + games_len = get_games(&games, games_len); + + switch (welcome_state) { + case NEW_SLOT: + strcpy(errorbuf, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Please upload \n" + " a game. \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " sprig.hackclub.com \n" + ); + break; + case RUN_GAME: { + char game_padding[] = " "; + char size_padding[] = " "; + + game_padding[ + 20 + - count_digits(games_i+1) + - count_digits(games_len) + - 8 // 7 at first + 1 slash + ] = '\0'; + + size_padding[ + 20 + - count_digits(GAME_SLOTS(games[games_i].size_b)) + - count_digits(MAX_SLOTS) + - 8 // 7 at first + 1 slash + ] = '\0'; + + // 6lines + char game_split_lines[] = { + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + }; + + for (int i = 0; i*17 < strlen(games[games_i].name); i++) { // 20 - 3 buffer = 17 + memcpy(&game_split_lines[i*21 + 1], &games[games_i].name[i*17], + strlen(games[games_i].name) - i*17 < 17 ? strlen(games[games_i].name) - i*17 : 17); + } + + + sprintf(errorbuf, + " \n" + " \n" + "%s" + " \n" + " Game: %d/%d%s\n" + " Size: %lu/%d%s\n" + " \n" + " W: PLAY \n" + " S: DELETE \n" + " <- A , D -> \n", + game_split_lines, + games_i + 1, games_len, game_padding, + GAME_SLOTS(games[games_i].size_b), MAX_SLOTS, size_padding); + break; + } + case DELETE_CONFIRM: { + strcpy(errorbuf, " \n" + " \n" + " \n" + " \n" + " \n" + " Do you really \n" + " want to delete \n" + " this game? \n" + " \n" + " \n" + " W: confirm \n" + " S: exit \n" + " \n" + " \n" + " sprig.hackclub.com \n" + ); + break; + } + } + + render_errorbuf(); + st7735_fill_start(); + render(st7735_fill_send); + st7735_fill_finish(); + + load_new_scripts(); + } // Wow, we can actually run a game now! diff --git a/firmware/spade/src/rpi/upload.h b/firmware/spade/src/rpi/upload.h index 401572742a..8223780746 100644 --- a/firmware/spade/src/rpi/upload.h +++ b/firmware/spade/src/rpi/upload.h @@ -1,28 +1,337 @@ #include "shared/sprig_engine/script.h" #include "hardware/flash.h" +#include static void core1_entry(void); +typedef enum { + Location_FLASH + // TODO: will add SD + // TODO: swap games into SD if flash overfilled +} Game_Location; + +typedef struct { + char name[100]; // todo: we need to set frontend limits on game names + Game_Location location; + uint8_t slot; + uint32_t size_b; +} Game; + +Game current_game = (Game) {}; +int slot = 0; + // rationale: half engine, half games? // NOTE: this has to be a multiple of 4096 (FLASH_SECTOR_SIZE) -#define FLASH_TARGET_OFFSET (800 * 1024) +#define SLOT_SIZE FLASH_SECTOR_SIZE +#define MAX_SLOTS 150 +#define FLASH_TARGET_START (800*1024) +#define FLASH_TARGET_OFFSET(slot_i) (FLASH_TARGET_START + slot_i * SLOT_SIZE) + +#define GAME_SLOTS(bytes) ((bytes + ARR_LEN(engine_script) - 1) / SLOT_SIZE + 1) + +#define METADATA_ENTRY_SIZE (256) // you can program up to one page at a time. (256 bytes). +#define METADATA_SIZE (32 * METADATA_ENTRY_SIZE) // first entry is for version // TODO: make sure this works when changing to 32 +#define METADATA_MAX_ENTRIES (METADATA_SIZE / METADATA_ENTRY_SIZE - 1) +#define METADATA_CONTENTS(index) ((const Game *) (XIP_BASE + FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE + index * METADATA_ENTRY_SIZE)) +#define METADATA_OFFSET ((uint32_t) (FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE)) + +// i think what happened here is only FLASH_TARGET_START was cast to a Game*. +// METADATA_SIZE was a signed int. this coerced METADATA_OFFSET into a signed int, +// which was then passed into a function that accepted a uint32_t. +// this messed up the pointer and caused it to overwrite application code in the flash (XIP) +// #define METADATA_OFFSET ((const Game*) FLASH_TARGET_START - METADATA_SIZE + METADATA_ENTRY_SIZE) + + +#define METADATA_START METADATA_CONTENTS(-1) -const uint8_t *flash_target_contents = (const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET); +#define FLASH_VERSION ((const char *) (XIP_BASE + FLASH_TARGET_START - METADATA_SIZE)) -uint16_t SPRIG_MAGIC[FLASH_PAGE_SIZE/2] = { 1337, 42, 69, 420, 420, 1337 }; +#define FLASH_TARGET_CONTENTS(slot_i) ((const uint8_t *) (XIP_BASE + FLASH_TARGET_OFFSET(slot_i))) -static const char *save_read(void) { - if (memcmp(&SPRIG_MAGIC, flash_target_contents, sizeof(SPRIG_MAGIC)) != 0) { +#define ZEROS(bytes) ((char[bytes]){}) + +static const int memory_is_ones(const void *memory, size_t count) { + // this is taken from gcc libiberty + register const unsigned char *s = (const unsigned char*)memory; + + while (count-- > 0) + { + if (*s++ != 0xFF) + return 0; + } + return 1; +} + +// sprig magic is still here because it's useful for debugging (binvis.io) + easier to not get rid of it lol +uint16_t SPRIG_MAGIC[6] = { 1337, 42, 69, 420, 420, 1337 }; + +static const char *save_read() { + if (memcmp(&SPRIG_MAGIC, FLASH_TARGET_CONTENTS(slot), sizeof(SPRIG_MAGIC)) != 0) { puts("no magic :("); return NULL; } // add a page to get what's after the magic - const char *save = flash_target_contents + FLASH_PAGE_SIZE; + const char *save = FLASH_TARGET_CONTENTS(slot) + FLASH_PAGE_SIZE; return save; } +static void erase_user_portion_of_flash_this_is_dangerous() { + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase( + (uint32_t) FLASH_TARGET_START - METADATA_SIZE - METADATA_ENTRY_SIZE, + SLOT_SIZE * MAX_SLOTS + METADATA_SIZE - METADATA_ENTRY_SIZE); // TODO: check that this is valid + restore_interrupts(interrupts); +} + +// save versions != spade versions +// increment in the value returned means different scheme for saving games +static int get_save_version(const char* version) { + if (memory_is_ones(version, METADATA_ENTRY_SIZE)) return 1; + if (strcmp(version, "1.1.0") == 0) return 2; + + // something weird happened!? + return -1; +} + +// must be run with core 1 disabled - irq risk +static void flash_write_save_version(const char* version) { + void *metadata_first_sector = malloc(FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector, METADATA_START, FLASH_SECTOR_SIZE); + strcpy(metadata_first_sector, version); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, FLASH_SECTOR_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, metadata_first_sector, FLASH_SECTOR_SIZE); + restore_interrupts(interrupts); + + free(metadata_first_sector); +} + +// must be run with core 1 disabled - irq risk +static int update_save_version() { // TODO: handle totally empty flash + int version = get_save_version(FLASH_VERSION); + if (version == get_save_version(SPADE_VERSION)) return 0; + + switch (version) { // recursive if need to update multiple versions. + case 1: // 1.0.0 or less + { + if (memcmp(&SPRIG_MAGIC, FLASH_TARGET_CONTENTS(0), sizeof(SPRIG_MAGIC)) == 0) { + // add flash metadata for first game + Game game = { + .slot = 0, + .size_b = strlen(FLASH_TARGET_CONTENTS(0)), + .name = "Legacy Game", + .location = Location_FLASH + }; + + void *metadata_first_sector = malloc(FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector, METADATA_START, FLASH_SECTOR_SIZE); + memcpy(metadata_first_sector + METADATA_ENTRY_SIZE, &game, sizeof(Game)); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, FLASH_SECTOR_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, metadata_first_sector, + FLASH_SECTOR_SIZE); + restore_interrupts(interrupts); + + free(metadata_first_sector); + } + + flash_write_save_version("1.1.0"); + return update_save_version(); + } + case 2: + default: + return 1; + } + +} + +// returns len of games. games should be pointer to array of games (Game**) +static int get_games(Game** games, int games_len) { + int games_i = -1; + + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + + volatile int is_zeros = memcmp(METADATA_CONTENTS(i), ZEROS(sizeof(Game)), sizeof(Game)); + volatile int is_ones = memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE); + if (is_ones) continue; + + if (games_len <= ++games_i) { + games_len *= 2; + *games = realloc(*games, games_len * sizeof(Game)); + } + + (*games)[games_i] = *METADATA_CONTENTS(i); + } + + return games_i + 1; +} + +static int get_available_game_slots() { + int available_metadata_slots = 0; + int available_flash_slots = MAX_SLOTS; + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + volatile int is_ones = memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE); + if (is_ones) + available_metadata_slots++; + else + available_flash_slots -= (int) GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + } + + // minimum of both + return available_flash_slots < available_metadata_slots ? available_flash_slots : available_metadata_slots; +} + + +static int get_first_open_flash_slot_at_end() { + int lowest_open_slot = -1; + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (!memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE)) { + int slot_after_entry = METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + + if (slot_after_entry > lowest_open_slot) { + lowest_open_slot = slot_after_entry; + } + } + } + return lowest_open_slot + 1; +} + +static int get_available_flash_slots_at_end() { + return MAX_SLOTS - get_first_open_flash_slot_at_end(); +} + +static int get_first_open_metadata_slot() { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE)) + return i; + } + + return -1; +} + +static void set_game(Game aGame) { + // this is fine for now. + // in future, include logic to swap games in SD to flash + current_game = aGame; + slot = aGame.slot; +} + +static int get_game_index_by_name(char* name) { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (!memory_is_ones(METADATA_CONTENTS(i), METADATA_ENTRY_SIZE) + && strcmp(METADATA_CONTENTS(i)->name, name) == 0) { + return i; + } + } + + return -1; +} + +static void delete_game(Game aGame) { + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + if (memcmp(METADATA_CONTENTS(i), &aGame, sizeof(Game)) == 0) { // this is the metadata slot + char ones[METADATA_ENTRY_SIZE]; + for (int j = 0; j < METADATA_ENTRY_SIZE; j++) { + ones[j] = 0xFF; + } + + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + memcpy(new_metadata + METADATA_ENTRY_SIZE * (i + 1), &ones, METADATA_ENTRY_SIZE); + + multicore_reset_core1(); + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + + free(new_metadata); + restore_interrupts(interrupts); + multicore_launch_core1(core1_entry); + } + } +} + +// TODO: proofread/test +// TODO: this does not work! +void consolidate_flash_games() { + multicore_reset_core1(); + + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + + int last_contiguous_game = 0; + + int prev_write = -1; + + for (;;) { + // infinite loop terminated by hitting end: + + int write = 0; + + int read = MAX_SLOTS; + int read_metadata_i = 0; + + for (int i = 0; i < METADATA_MAX_ENTRIES; i++) { + + int empty = 1; + + for (int j = 0; j < METADATA_MAX_ENTRIES; j++) { + if (METADATA_CONTENTS(j)->slot == + METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b)) { + empty = 0; + } + } + + if (!empty) continue; + + write = METADATA_CONTENTS(i)->slot + GAME_SLOTS(METADATA_CONTENTS(i)->size_b); + + if (write == prev_write) { + + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + + restore_interrupts(interrupts); + + free(new_metadata); + multicore_launch_core1(core1_entry); + return; + } + + for (int j = 0; j < METADATA_MAX_ENTRIES; j++) { + if (METADATA_CONTENTS(j)->slot > write && METADATA_CONTENTS(j)->slot < read) { + read = METADATA_CONTENTS(j)->slot; + read_metadata_i = j; + } + } + + prev_write = write; + + break; + } + + for (int i = 0; i < GAME_SLOTS(METADATA_CONTENTS(read_metadata_i)->size_b); i++) { + uint32_t interrupts = save_and_disable_interrupts(); + + flash_range_erase(FLASH_TARGET_OFFSET(read + i), SLOT_SIZE); + flash_range_program(FLASH_TARGET_OFFSET(read + FLASH_SECTOR_SIZE * i), FLASH_TARGET_CONTENTS(write + i), + SLOT_SIZE); + + restore_interrupts(interrupts); + } + + ((Game*) (new_metadata + METADATA_ENTRY_SIZE*read))->slot = write; + + } +} typedef enum { + UplProg_Init, UplProg_Header, UplProg_Body, } UplProg; @@ -31,16 +340,20 @@ static struct { uint32_t len, len_i; char buf[256]; int page; + char name[100]; + uint8_t name_i; } upl_state = {0}; static void upl_flush_buf(void) { - uint32_t interrupts = save_and_disable_interrupts(); - flash_range_program(FLASH_TARGET_OFFSET + (upl_state.page++) * 256, + puts("wtf?? 6"); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_program(FLASH_TARGET_OFFSET(slot) + (upl_state.page++) * 256, (void *)upl_state.buf, 256); restore_interrupts(interrupts); memset(upl_state.buf, 0, sizeof(upl_state.buf)); - printf("wrote page (%d/%d)\n", + printf("wrote page (%d/%lu)\n", upl_state.page, (upl_state.len/(FLASH_PAGE_SIZE + 1))); } @@ -49,58 +362,120 @@ static int upl_stdin_read(void) { memset(&upl_state, 0, sizeof(upl_state)); int timeout = 1000; // 1ms; we're already in upload mode + for (;;) { int c = getchar_timeout_us(timeout); if (c == PICO_ERROR_TIMEOUT) return 0; switch (upl_state.prog) { + case UplProg_Init: { + // irqs on other core? + multicore_reset_core1(); + + upl_state.prog = UplProg_Header; + } // falls through case UplProg_Header: { - ((char *)(&upl_state.len))[upl_state.len_i++] = c; - if (upl_state.len_i >= sizeof(uint32_t)) { - printf("ok reading %d chars\n", upl_state.len); - upl_state.prog = UplProg_Body; - upl_state.len_i = 0; - upl_state.page = 1; // skip first, that's for magic - - int char_len = upl_state.len + sizeof (engine_script); // sizeof script includes the null term, we still need to remove from script - upl_state.len = char_len; - // one to round up, one for magic - int page_len = (char_len/FLASH_PAGE_SIZE + 2) * FLASH_PAGE_SIZE ; - int sector_len = (page_len/FLASH_SECTOR_SIZE + 1) * FLASH_SECTOR_SIZE; - - // irqs on other core? - multicore_reset_core1(); - - uint32_t interrupts = save_and_disable_interrupts(); - flash_range_erase(FLASH_TARGET_OFFSET, sector_len); - restore_interrupts(interrupts); + puts("wahoo header"); - for (int i = 0; i < sizeof(engine_script) - 1; i++) { - upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = engine_script[i]; - if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { - puts("flushin buf (wit da code!)"); - upl_flush_buf(); - } + if (upl_state.name_i < sizeof(upl_state.name) / sizeof(char)) // read game + { + ((char *) (&upl_state.name))[upl_state.name_i++] = c; + puts(upl_state.name); } + else { + ((char *) (&upl_state.len))[upl_state.len_i++] = c; + if (upl_state.len_i >= sizeof(uint32_t)) { + printf("ok reading %lu chars\n", upl_state.len); + upl_state.prog = UplProg_Body; + upl_state.len_i = 0; + upl_state.page = 1; // skip first, that's for magic - puts("cleared flash"); - } + { + Game game; + strcpy(game.name, upl_state.name); + game.size_b = upl_state.len; + game.location = Location_FLASH; + + + // not enough slots + if (get_available_game_slots() < GAME_SLOTS(upl_state.len)) { + puts("no available game slots!"); + printf("we need %lu slots but we only have %d",GAME_SLOTS(upl_state.len), get_available_game_slots()); + return 0; // ERROR! + } else if (get_available_flash_slots_at_end() < GAME_SLOTS(upl_state.len)) { + puts("consolidating!"); + consolidate_flash_games(); + game.slot = get_first_open_flash_slot_at_end(); + } else { + game.slot = get_first_open_flash_slot_at_end(); + } + + slot = game.slot; + + int metadata_i; + int search_result = get_game_index_by_name(game.name); + + if (search_result != -1) { + metadata_i = search_result; + } else { + metadata_i = get_first_open_metadata_slot(); + } + void *new_metadata = malloc(METADATA_SIZE); + memcpy(new_metadata, METADATA_START, METADATA_SIZE); + memcpy(new_metadata + METADATA_ENTRY_SIZE * (metadata_i + 1), &game, sizeof(Game)); + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(METADATA_OFFSET - METADATA_ENTRY_SIZE, METADATA_SIZE); + flash_range_program(METADATA_OFFSET - METADATA_ENTRY_SIZE, new_metadata, METADATA_SIZE); + restore_interrupts(interrupts); + + printf("metadata_i: %d,\n" + "slot: %d\n" + "size_b: %lu", metadata_i, slot, game.size_b); + + free(new_metadata); + } + + uint32_t char_len = upl_state.len + + sizeof(engine_script); // sizeof script includes the null term, we still need to remove from script + upl_state.len = char_len; + // one to round up, one for magic + uint32_t page_len = (char_len / FLASH_PAGE_SIZE + 2) * FLASH_PAGE_SIZE; + uint32_t sector_len = (page_len / FLASH_SECTOR_SIZE + 1) * FLASH_SECTOR_SIZE; + + uint32_t interrupts = save_and_disable_interrupts(); + flash_range_erase(FLASH_TARGET_OFFSET(slot), sector_len); + restore_interrupts(interrupts); + + for (int i = 0; i < sizeof(engine_script) - 1; i++) { + upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = engine_script[i]; + if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { + puts("flushin buf (wit da code!)"); + upl_flush_buf(); + } + } + + puts("cleared flash"); + } + } } break; case UplProg_Body: { - // printf("upl char (%d/%d)\n", upl_state.len_i, upl_state.len); + + // printf("upl char (%d/%d)\n", upl_state.len_i, upl_state.len); upl_state.buf[upl_state.len_i++ % FLASH_PAGE_SIZE] = c; + if (upl_state.len_i % FLASH_PAGE_SIZE == 0) { puts("flushin buf"); upl_flush_buf(); } - if (upl_state.len_i == upl_state.len - 1) { + if (upl_state.len_i == upl_state.len - 1) { upl_flush_buf(); uint32_t interrupts = save_and_disable_interrupts(); - flash_range_program(FLASH_TARGET_OFFSET, (void *)SPRIG_MAGIC, FLASH_PAGE_SIZE); + flash_range_program(FLASH_TARGET_OFFSET(slot), (void *)SPRIG_MAGIC, FLASH_PAGE_SIZE); restore_interrupts(interrupts); - + // printf("read in %d chars\n", upl_state.len); puts("ALL_GOOD"); memset(&upl_state, 0, sizeof(upl_state)); diff --git a/firmware/spade/src/version.json b/firmware/spade/src/version.json index 688e939808..07cd7643a2 100644 --- a/firmware/spade/src/version.json +++ b/firmware/spade/src/version.json @@ -1,3 +1,3 @@ { - "version": "1.0.0" + "version": "1.1.0" } \ No newline at end of file diff --git a/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl b/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl index 1e0bb29ace..22dd26df86 100644 --- a/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl +++ b/hardware/mainboard_PCB/kicad/sprig_console.kicad_prl @@ -3,10 +3,12 @@ "active_layer": 31, "active_layer_preset": "", "auto_track_width": true, + "hidden_netclasses": [], "hidden_nets": [], "high_contrast_mode": 0, "net_color_mode": 1, "opacity": { + "images": 0.6, "pads": 1.0, "tracks": 1.0, "vias": 1.0, @@ -65,8 +67,14 @@ "visible_layers": "ffffeff_ffffffff", "zone_display_mode": 0 }, + "git": { + "repo_password": "", + "repo_type": "", + "repo_username": "", + "ssh_key": "" + }, "meta": { - "filename": "pigame_pico.kicad_prl", + "filename": "sprig_console.kicad_prl", "version": 3 }, "project": { diff --git a/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro b/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro index 660288e18b..78dfe06ed1 100644 --- a/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro +++ b/hardware/mainboard_PCB/kicad/sprig_console.kicad_pro @@ -1,5 +1,6 @@ { "board": { + "3dviewports": [], "design_settings": { "defaults": { "board_outline_line_width": 0.09999999999999999, @@ -135,7 +136,15 @@ "zones_allow_external_fillets": false, "zones_use_no_outline": true }, - "layer_presets": [] + "ipc2581": { + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "" + }, + "layer_presets": [], + "viewports": [] }, "boards": [], "cvpcb": { @@ -322,15 +331,21 @@ "bus_label_syntax": "error", "bus_to_bus_conflict": "error", "bus_to_net_conflict": "error", + "conflicting_netclasses": "error", "different_unit_footprint": "error", "different_unit_net": "error", "duplicate_reference": "error", "duplicate_sheet_names": "error", + "endpoint_off_grid": "warning", "extra_units": "error", "global_label_dangling": "warning", "hier_label_mismatch": "error", "label_dangling": "error", "lib_symbol_issues": "warning", + "missing_bidi_pin": "warning", + "missing_input_pin": "warning", + "missing_power_pin": "error", + "missing_unit": "warning", "multiple_net_names": "warning", "net_not_bus_member": "warning", "no_connect_connected": "warning", @@ -340,6 +355,7 @@ "pin_to_pin": "warning", "power_pin_not_driven": "error", "similar_labels": "warning", + "simulation_model_issue": "ignore", "unannotated": "error", "unit_value_mismatch": "error", "unresolved_variable": "error", @@ -351,13 +367,13 @@ "pinned_symbol_libs": [] }, "meta": { - "filename": "pigame_pico.kicad_pro", + "filename": "sprig_console.kicad_pro", "version": 1 }, "net_settings": { "classes": [ { - "bus_width": 12.0, + "bus_width": 12, "clearance": 0.2, "diff_pair_gap": 0.25, "diff_pair_via_gap": 0.25, @@ -371,28 +387,93 @@ "track_width": 0.25, "via_diameter": 0.8, "via_drill": 0.4, - "wire_width": 6.0 + "wire_width": 6 } ], "meta": { - "version": 2 + "version": 3 }, - "net_colors": null + "net_colors": null, + "netclass_assignments": null, + "netclass_patterns": [] }, "pcbnew": { "last_paths": { "gencad": "", "idf": "", "netlist": "", + "plot": "", + "pos_files": "", "specctra_dsn": "", "step": "", + "svg": "", "vrml": "" }, "page_layout_descr_file": "" }, "schematic": { "annotate_start_num": 0, + "bom_fmt_presets": [], + "bom_fmt_settings": { + "field_delimiter": ",", + "keep_line_breaks": false, + "keep_tabs": false, + "name": "CSV", + "ref_delimiter": ",", + "ref_range_delimiter": "", + "string_delimiter": "\"" + }, + "bom_presets": [], + "bom_settings": { + "exclude_dnp": false, + "fields_ordered": [ + { + "group_by": false, + "label": "Reference", + "name": "Reference", + "show": true + }, + { + "group_by": true, + "label": "Value", + "name": "Value", + "show": true + }, + { + "group_by": false, + "label": "Datasheet", + "name": "Datasheet", + "show": true + }, + { + "group_by": false, + "label": "Footprint", + "name": "Footprint", + "show": true + }, + { + "group_by": false, + "label": "Qty", + "name": "${QUANTITY}", + "show": true + }, + { + "group_by": true, + "label": "DNP", + "name": "${DNP}", + "show": true + } + ], + "filter_string": "", + "group_symbols": true, + "name": "Grouped By Value", + "sort_asc": true, + "sort_field": "Reference" + }, + "connection_grid_size": 50.0, "drawing": { + "dashed_lines_dash_length_ratio": 12.0, + "dashed_lines_gap_length_ratio": 3.0, "default_line_thickness": 6.0, "default_text_size": 50.0, "field_names": [], @@ -403,6 +484,11 @@ "intersheets_ref_suffix": "", "junction_size_choice": 3, "label_size_ratio": 0.375, + "operating_point_overlay_i_precision": 3, + "operating_point_overlay_i_range": "~A", + "operating_point_overlay_v_precision": 3, + "operating_point_overlay_v_range": "~V", + "overbar_offset_ratio": 1.23, "pin_symbol_size": 25.0, "text_offset_ratio": 0.15 }, @@ -424,7 +510,12 @@ "page_layout_descr_file": "", "plot_directory": "", "spice_adjust_passive_values": false, + "spice_current_sheet_as_root": false, "spice_external_command": "spice \"%I\"", + "spice_model_current_sheet_as_root": true, + "spice_save_all_currents": false, + "spice_save_all_dissipations": false, + "spice_save_all_voltages": false, "subpart_first_id": 65, "subpart_id_separator": 0 }, diff --git a/scripts/gardenshed/gdb-server.sh b/scripts/gardenshed/gdb-server.sh index 664c4c99a3..112f404afc 100755 --- a/scripts/gardenshed/gdb-server.sh +++ b/scripts/gardenshed/gdb-server.sh @@ -1,3 +1,3 @@ #!/bin/bash -sudo openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program ./spade.elf verify reset" +sudo openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "set USE_CORE 0" -c "program ./spade.elf verify reset" diff --git a/src/components/navbar-editor.tsx b/src/components/navbar-editor.tsx index 3102f2ef7c..b31c92dd81 100644 --- a/src/components/navbar-editor.tsx +++ b/src/components/navbar-editor.tsx @@ -350,7 +350,13 @@ export default function EditorNavbar(props: EditorNavbarProps) { spinnyIcon={uploadState.value === "LOADING"} loading={uploadState.value === "LOADING"} onClick={() => - upload(codeMirror.value?.state.doc.toString() ?? "") + upload(codeMirror.value?.state.doc.toString() ?? "", + props.persistenceState.value.kind == "PERSISTED" + && props.persistenceState.value.game != "LOADING" + ? props.persistenceState.value.game.name + : props.persistenceState.value.kind == "SHARED" ? props.persistenceState.value.name + : "Untitled Game" + ) } > Run on Device diff --git a/src/lib/upload.ts b/src/lib/upload.ts index 36b3f6af8f..06b46312e9 100644 --- a/src/lib/upload.ts +++ b/src/lib/upload.ts @@ -32,7 +32,7 @@ const getPort = async (): Promise => { export const logSerialOutput = (value: string) => (value.trim().length > 0) && console.log(`%c< ${value.trim()}`, 'color: #999') -export const uploadToSerial = async (message: string, +export const uploadToSerial = async (name: string, message: string, writer: WritableStreamDefaultWriter, reader: ReadableStreamDefaultReader) => { @@ -61,6 +61,14 @@ export const uploadToSerial = async (message: string, console.log('[UPLOAD > SERIAL] Checkpoint 2') await writer.ready + + console.log('[UPLOAD > SERIAL] Checkpoint 2 - writing name') + // send name + padding to total 128b + // TODO: game titles shouldn't be able to have special characters + const nameString = new Uint8Array(100) + new TextEncoder().encodeInto(name + "\0".repeat(100 - name.length), nameString) + await writer.write(nameString) + console.log('[UPLOAD > SERIAL] Checkpoint 2 - writing length') await writer.write(new Uint32Array([ buf.length ]).buffer) @@ -132,7 +140,7 @@ export const getIsLegacySerial = async ( } } -export const upload = async (code: string): Promise => { +export const upload = async (code: string, name: string): Promise => { if (uploadState.value === 'LOADING') return uploadState.value = 'LOADING' @@ -173,7 +181,7 @@ export const upload = async (code: string): Promise => { console.log("[UPLOAD] Version up to date!") } - await uploadToSerial(code, writer, reader) + await uploadToSerial(name, code, writer, reader) console.log('[UPLOAD] Waiting on stream close and writer lock release...') //reader.releaseLock()