From a286a6a791d93d8ceedf9fbf7171afc0444d462b Mon Sep 17 00:00:00 2001 From: dejvino Date: Wed, 29 Jan 2020 18:30:50 +0100 Subject: [PATCH] Import of adapted loboris/ESP32_ePaper_example repository --- .gitignore | 33 + README.md | 179 +- components/epaper/DefaultFont.c | 333 +++ components/epaper/DejaVuSans18.c | 322 +++ components/epaper/DejaVuSans24.c | 331 +++ components/epaper/EPD.c | 2322 ++++++++++++++++ components/epaper/EPD.h | 571 ++++ components/epaper/EPDspi.c | 759 ++++++ components/epaper/EPDspi.h | 86 + components/epaper/SmallFont.c | 120 + components/epaper/Ubuntu16.c | 331 +++ components/epaper/comic24.c | 331 +++ components/epaper/component.mk | 7 + components/epaper/dejavuX.c | 718 +++++ components/epaper/minya24.c | 331 +++ components/epaper/tooney32.c | 331 +++ components/mkspiffs/.travis.yml | 24 + components/mkspiffs/Makefile.projbuild | 14 + components/mkspiffs/component.mk | 6 + components/mkspiffs/src/Makefile.original | 93 + components/mkspiffs/src/README.md | 80 + components/mkspiffs/src/appveyor.yml | 28 + components/mkspiffs/src/main.cpp | 646 +++++ components/mkspiffs/src/mkspiffs | Bin 0 -> 145776 bytes components/mkspiffs/src/spiffs/esp_spiffs.c | 138 + components/mkspiffs/src/spiffs/esp_spiffs.h | 43 + components/mkspiffs/src/spiffs/spiffs.h | 813 ++++++ components/mkspiffs/src/spiffs/spiffs_cache.c | 314 +++ components/mkspiffs/src/spiffs/spiffs_check.c | 995 +++++++ .../mkspiffs/src/spiffs/spiffs_config.h | 361 +++ components/mkspiffs/src/spiffs/spiffs_gc.c | 606 +++++ .../mkspiffs/src/spiffs/spiffs_hydrogen.c | 1405 ++++++++++ .../mkspiffs/src/spiffs/spiffs_nucleus.c | 2327 ++++++++++++++++ .../mkspiffs/src/spiffs/spiffs_nucleus.h | 797 ++++++ components/mkspiffs/src/tclap/Arg.h | 692 +++++ components/mkspiffs/src/tclap/ArgException.h | 200 ++ components/mkspiffs/src/tclap/ArgTraits.h | 87 + components/mkspiffs/src/tclap/COPYING | 25 + components/mkspiffs/src/tclap/CmdLine.h | 633 +++++ .../mkspiffs/src/tclap/CmdLineInterface.h | 150 ++ components/mkspiffs/src/tclap/CmdLineOutput.h | 74 + components/mkspiffs/src/tclap/Constraint.h | 68 + components/mkspiffs/src/tclap/DocBookOutput.h | 299 +++ components/mkspiffs/src/tclap/HelpVisitor.h | 76 + .../mkspiffs/src/tclap/IgnoreRestVisitor.h | 52 + components/mkspiffs/src/tclap/MultiArg.h | 433 +++ .../mkspiffs/src/tclap/MultiSwitchArg.h | 216 ++ .../src/tclap/OptionalUnlabeledTracker.h | 62 + .../mkspiffs/src/tclap/StandardTraits.h | 208 ++ components/mkspiffs/src/tclap/StdOutput.h | 298 +++ components/mkspiffs/src/tclap/SwitchArg.h | 266 ++ .../mkspiffs/src/tclap/UnlabeledMultiArg.h | 301 +++ .../mkspiffs/src/tclap/UnlabeledValueArg.h | 340 +++ components/mkspiffs/src/tclap/ValueArg.h | 425 +++ .../mkspiffs/src/tclap/ValuesConstraint.h | 148 + .../mkspiffs/src/tclap/VersionVisitor.h | 81 + components/mkspiffs/src/tclap/Visitor.h | 53 + components/mkspiffs/src/tclap/XorHandler.h | 166 ++ .../mkspiffs/src/tclap/ZshCompletionOutput.h | 323 +++ components/spidriver/component.mk | 7 + components/spidriver/spi_master_lobo.c | 1153 ++++++++ components/spidriver/spi_master_lobo.h | 355 +++ components/spiffs/component.mk | 7 + components/spiffs/esp_spiffs.c | 138 + components/spiffs/esp_spiffs.h | 43 + components/spiffs/list.c | 251 ++ components/spiffs/list.h | 60 + components/spiffs/mutex.c | 102 + components/spiffs/mutex.h | 53 + components/spiffs/spiffs.h | 813 ++++++ components/spiffs/spiffs_cache.c | 314 +++ components/spiffs/spiffs_check.c | 995 +++++++ components/spiffs/spiffs_config.h | 367 +++ components/spiffs/spiffs_gc.c | 606 +++++ components/spiffs/spiffs_hydrogen.c | 1405 ++++++++++ components/spiffs/spiffs_nucleus.c | 2327 ++++++++++++++++ components/spiffs/spiffs_nucleus.h | 797 ++++++ components/spiffs/spiffs_vfs.c | 878 ++++++ components/spiffs/spiffs_vfs.h | 23 + components/spiffs_image/Makefile.projbuild | 26 + components/spiffs_image/component.mk | 6 + .../spiffs_image/image/fonts/BigFont.fon | Bin 0 -> 3052 bytes .../spiffs_image/image/fonts/DejaVuSans12.fon | Bin 0 -> 1166 bytes .../spiffs_image/image/fonts/DejaVuSans18.fon | Bin 0 -> 1836 bytes .../spiffs_image/image/fonts/DejaVuSans24.fon | Bin 0 -> 2732 bytes .../spiffs_image/image/fonts/DotMatrix_M.fon | Bin 0 -> 4192 bytes .../spiffs_image/image/fonts/Grotesk24x48.fon | Bin 0 -> 13692 bytes .../spiffs_image/image/fonts/SmallFont.fon | Bin 0 -> 1152 bytes .../spiffs_image/image/fonts/Ubuntu.fon | Bin 0 -> 9132 bytes .../spiffs_image/image/fonts/arial_bold.fon | Bin 0 -> 3052 bytes components/spiffs_image/image/fonts/ocrfont.c | 119 + .../image/fonts/swiss721_outline.fon | Bin 0 -> 3052 bytes .../spiffs_image/image/images/Flintstones.jpg | Bin 0 -> 26319 bytes .../image/images/animal-silhouettes.jpg | Bin 0 -> 11904 bytes .../image/images/evolution-of-human.jpg | Bin 0 -> 12964 bytes .../image/images/girl_silhouettes.jpg | Bin 0 -> 11697 bytes .../image/images/people_silhouettes.jpg | Bin 0 -> 17222 bytes .../image/images/silhouettes-dancing.jpg | Bin 0 -> 15302 bytes components/spiffs_image/image/spiffs.info | 11 + components/spiffs_image/spiffs_image.img | Bin 0 -> 1048576 bytes main/Kconfig.projbuild | 49 + main/component.mk | 7 + main/ePaper.c | 689 +++++ main/ePaper.h | 73 + main/img1.h | 298 +++ main/img2.h | 298 +++ main/img3.h | 298 +++ main/img_hacking.c | 2382 +++++++++++++++++ partitions_example.csv | 6 + sdkconfig.defaults | 10 + 110 files changed, 36435 insertions(+), 2 deletions(-) create mode 100644 components/epaper/DefaultFont.c create mode 100644 components/epaper/DejaVuSans18.c create mode 100644 components/epaper/DejaVuSans24.c create mode 100644 components/epaper/EPD.c create mode 100644 components/epaper/EPD.h create mode 100644 components/epaper/EPDspi.c create mode 100644 components/epaper/EPDspi.h create mode 100644 components/epaper/SmallFont.c create mode 100644 components/epaper/Ubuntu16.c create mode 100644 components/epaper/comic24.c create mode 100644 components/epaper/component.mk create mode 100644 components/epaper/dejavuX.c create mode 100644 components/epaper/minya24.c create mode 100644 components/epaper/tooney32.c create mode 100644 components/mkspiffs/.travis.yml create mode 100644 components/mkspiffs/Makefile.projbuild create mode 100644 components/mkspiffs/component.mk create mode 100644 components/mkspiffs/src/Makefile.original create mode 100644 components/mkspiffs/src/README.md create mode 100644 components/mkspiffs/src/appveyor.yml create mode 100644 components/mkspiffs/src/main.cpp create mode 100755 components/mkspiffs/src/mkspiffs create mode 100644 components/mkspiffs/src/spiffs/esp_spiffs.c create mode 100644 components/mkspiffs/src/spiffs/esp_spiffs.h create mode 100644 components/mkspiffs/src/spiffs/spiffs.h create mode 100644 components/mkspiffs/src/spiffs/spiffs_cache.c create mode 100644 components/mkspiffs/src/spiffs/spiffs_check.c create mode 100644 components/mkspiffs/src/spiffs/spiffs_config.h create mode 100644 components/mkspiffs/src/spiffs/spiffs_gc.c create mode 100644 components/mkspiffs/src/spiffs/spiffs_hydrogen.c create mode 100644 components/mkspiffs/src/spiffs/spiffs_nucleus.c create mode 100644 components/mkspiffs/src/spiffs/spiffs_nucleus.h create mode 100644 components/mkspiffs/src/tclap/Arg.h create mode 100644 components/mkspiffs/src/tclap/ArgException.h create mode 100644 components/mkspiffs/src/tclap/ArgTraits.h create mode 100644 components/mkspiffs/src/tclap/COPYING create mode 100644 components/mkspiffs/src/tclap/CmdLine.h create mode 100644 components/mkspiffs/src/tclap/CmdLineInterface.h create mode 100644 components/mkspiffs/src/tclap/CmdLineOutput.h create mode 100644 components/mkspiffs/src/tclap/Constraint.h create mode 100644 components/mkspiffs/src/tclap/DocBookOutput.h create mode 100644 components/mkspiffs/src/tclap/HelpVisitor.h create mode 100644 components/mkspiffs/src/tclap/IgnoreRestVisitor.h create mode 100644 components/mkspiffs/src/tclap/MultiArg.h create mode 100644 components/mkspiffs/src/tclap/MultiSwitchArg.h create mode 100644 components/mkspiffs/src/tclap/OptionalUnlabeledTracker.h create mode 100644 components/mkspiffs/src/tclap/StandardTraits.h create mode 100644 components/mkspiffs/src/tclap/StdOutput.h create mode 100644 components/mkspiffs/src/tclap/SwitchArg.h create mode 100644 components/mkspiffs/src/tclap/UnlabeledMultiArg.h create mode 100644 components/mkspiffs/src/tclap/UnlabeledValueArg.h create mode 100644 components/mkspiffs/src/tclap/ValueArg.h create mode 100644 components/mkspiffs/src/tclap/ValuesConstraint.h create mode 100644 components/mkspiffs/src/tclap/VersionVisitor.h create mode 100644 components/mkspiffs/src/tclap/Visitor.h create mode 100644 components/mkspiffs/src/tclap/XorHandler.h create mode 100644 components/mkspiffs/src/tclap/ZshCompletionOutput.h create mode 100644 components/spidriver/component.mk create mode 100644 components/spidriver/spi_master_lobo.c create mode 100644 components/spidriver/spi_master_lobo.h create mode 100644 components/spiffs/component.mk create mode 100644 components/spiffs/esp_spiffs.c create mode 100644 components/spiffs/esp_spiffs.h create mode 100644 components/spiffs/list.c create mode 100644 components/spiffs/list.h create mode 100644 components/spiffs/mutex.c create mode 100644 components/spiffs/mutex.h create mode 100644 components/spiffs/spiffs.h create mode 100644 components/spiffs/spiffs_cache.c create mode 100644 components/spiffs/spiffs_check.c create mode 100644 components/spiffs/spiffs_config.h create mode 100644 components/spiffs/spiffs_gc.c create mode 100644 components/spiffs/spiffs_hydrogen.c create mode 100644 components/spiffs/spiffs_nucleus.c create mode 100644 components/spiffs/spiffs_nucleus.h create mode 100644 components/spiffs/spiffs_vfs.c create mode 100644 components/spiffs/spiffs_vfs.h create mode 100644 components/spiffs_image/Makefile.projbuild create mode 100644 components/spiffs_image/component.mk create mode 100644 components/spiffs_image/image/fonts/BigFont.fon create mode 100644 components/spiffs_image/image/fonts/DejaVuSans12.fon create mode 100644 components/spiffs_image/image/fonts/DejaVuSans18.fon create mode 100644 components/spiffs_image/image/fonts/DejaVuSans24.fon create mode 100644 components/spiffs_image/image/fonts/DotMatrix_M.fon create mode 100644 components/spiffs_image/image/fonts/Grotesk24x48.fon create mode 100644 components/spiffs_image/image/fonts/SmallFont.fon create mode 100644 components/spiffs_image/image/fonts/Ubuntu.fon create mode 100644 components/spiffs_image/image/fonts/arial_bold.fon create mode 100644 components/spiffs_image/image/fonts/ocrfont.c create mode 100644 components/spiffs_image/image/fonts/swiss721_outline.fon create mode 100644 components/spiffs_image/image/images/Flintstones.jpg create mode 100644 components/spiffs_image/image/images/animal-silhouettes.jpg create mode 100644 components/spiffs_image/image/images/evolution-of-human.jpg create mode 100644 components/spiffs_image/image/images/girl_silhouettes.jpg create mode 100644 components/spiffs_image/image/images/people_silhouettes.jpg create mode 100644 components/spiffs_image/image/images/silhouettes-dancing.jpg create mode 100644 components/spiffs_image/image/spiffs.info create mode 100644 components/spiffs_image/spiffs_image.img create mode 100644 main/Kconfig.projbuild create mode 100644 main/component.mk create mode 100644 main/ePaper.c create mode 100644 main/ePaper.h create mode 100644 main/img1.h create mode 100644 main/img2.h create mode 100644 main/img3.h create mode 100644 main/img_hacking.c create mode 100644 partitions_example.csv create mode 100644 sdkconfig.defaults diff --git a/.gitignore b/.gitignore index 46f42f8..8cacd03 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,36 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps +.config +*.o +*.pyc +*.a +*.d + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +sdkconfig +sdkconfig.old +sdkconfig.lobo +.cproject +.project +.settings +BUILD +build/ +temp/ +local/ +*.dis +*.elf +*.map +**/.DS_Store diff --git a/README.md b/README.md index 28763e6..522af21 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,177 @@ -# lilybook -Little ePaper reader for ESP32 + +### ePaper library for ESP32 + +--- + + +#### Features + +* Support for **GDEH029A1** / **SSD1608** based ePaper modules in 4-wire SPI mode. Support for other controllers will be added later +* **emulated** 4-bit gray scale mode +* **SPI displays oriented SPI driver library** based on *spi-master* driver +* Combined **DMA SPI** transfer mode and **direct SPI** for maximal speed +* **4-bit Grayscale mode** or **1-bit b/w mode** can be selected during runtime +* SPI speeds up to **20 MHz** are tested and works without problems +* **Demo application** included which demonstrates most of the library features + + +* **Graphics drawing functions**: + * **EPD_drawPixel** Draw pixel at given x,y coordinates + * **EPD_drawLine** Draw line between two points + * **EPD_drawFastVLine**, **EPD_drawFastHLine** Draw vertical or horizontal line of given lenght + * **EPD_drawLineByAngle** Draw line on screen from (x,y) point at given angle + * **EPD_drawRect**, **EPD_fillRect** Draw rectangle on screen or fill given rectangular screen region with color + * **EPD_drawRoundRect**, **EPD_fillRoundRect** Draw rectangle on screen or fill given rectangular screen region with color with rounded corners + * **EPD_drawCircle**, **EPD_fillCircle** Draw or fill circle on screen + * **EPD_drawEllipse**, **EPD_fillEllipse** Draw or fill ellipse on screen + * **EPD_drawTriangel**, **EPD_fillTriangle** Draw or fill triangle on screen + * **EPD_drawArc** Draw circle arc on screen, from ~ to given angles, with given thickness. Can be outlined with different color + * **EPD_drawPolygon** Draw poligon on screen with given number of sides (3~60). Can be outlined with different color and rotated by given angle. +* **Fonts**: + * **fixed** width and proportional fonts are supported; 8 fonts embeded + * unlimited number of **fonts from file** + * **7-segment vector font** with variable width/height is included (only numbers and few characters) + * Proportional fonts can be used in fixed width mode. + * Related functions: + * **EPD_setFont** Set current font from one of embeded fonts or font file + * **EPD_getfontsize** Returns current font height & width in pixels. + * **EPD_getfontheight** Returns current font height in pixels. + * **set_7seg_font_atrib** Set atributes for 7 segment vector font + * **getFontCharacters** Get all font's characters to buffer +* **String write function**: + * **EPD_print** Write text to display. + * Strings can be printed at **any angle**. Rotation of the displayed text depends on *font_ratate* variable (0~360) + * if *font_transparent* variable is set to 1, no background pixels will be printed + * If the text does not fit the screen/window width it will be clipped ( if *text_wrap=0* ), or continued on next line ( if *text_wrap=1* ) + * Two special characters are allowed in strings: *\r* CR (0x0D), clears the display to EOL, *\n* LF (ox0A), continues to the new line, x=0 + * Special values can be entered for X position: + * *CENTER* centers the text + * *RIGHT* right justifies the text horizontaly + * *LASTX* continues from last X position; offset can be used: *LASTX+n* + * Special values can be entered for Y: + * *CENTER* centers the text verticaly + * *BOTTOM* bottom justifies the text + * *LASTY* continues from last Y position; offset can be used: *LASTY+n* + * **EPD_getStringWidth** Returns the string width in pixels based on current font characteristics. Useful for positioning strings on the screen. + * **EPD_clearStringRect** Fills the rectangle occupied by string with current background color +* **Images**: + * **EPD_jpg_image** Decodes and displays JPG images + * Limits: + * Baseline only. Progressive and Lossless JPEG format are not supported. + * Image size: Up to 65520 x 65520 pixels + * Color space: YCbCr three components only. Gray scale image is not supported. + * Sampling factor: 4:4:4, 4:2:2 or 4:2:0. + * Can display the image **from file** or **memory buffer** + * Image can be **scaled** by factor 0 ~ 3 (1/1, 1/2, 1/4 or 1/8) + * Image is displayed from X,Y position on screen/window: + * X: image left position; constants CENTER & RIGHT can be used; *negative* value is accepted + * Y: image top position; constants CENTER & BOTTOM can be used; *negative* value is accepted + * Image is converted to **4-bit Gray Scale mode** +* **Other display functions**: + * **EPD_fillScreen** Fill the whole screen with black, white or gray scale +* **compile_font_file** Function which compiles font c source file to font file which can be used in *EPD_setFont()* function to select external font. Created file have the same name as source file and extension *.fnt* + + +* **Global wariables** + * **orientation** current screen orientation + * **font_ratate** current font rotate angle (0~395) + * **font_transparent** if not 0 draw fonts transparent + * **font_forceFixed** if not zero force drawing proportional fonts with fixed width + * **text_wrap** if not 0 wrap long text to the new line, else clip + * **_fg** current foreground color for fonts + * **_bg** current background for non transparent fonts + * **_angleOffset** angle offset for arc, polygon and line by angle functions + * **image_debug** print debug messages during image decode if set to 1 + * **cfont** Currently used font structure + * **EPD_X** X position of the next character after EPD_print() function + * **EPD_Y** Y position of the next character after EPD_print() function + * **_gs** use 4-bit Gray scale if set to 1 + * **_width** screen width (larger dimension) in pixels + * **_height** screen height (smaller dimension) in pixels + +--- + +Full functions **syntax and descriptions** can be found in *EPD.h* and *EPDspi.h* files. + +Full **demo application**, well documented, is included, please **analyze it** to learn how to use the library functions. + +--- + +#### Connecting the display + +To run the demo, attach display module to ESP32. Default pins used are: +* mosi: 23 +* sck: 18 +* CS: 5 (display CS) +* DC: 26 (display DC) +* RST: 27 (display RESET) +* BUSY: 32 (display BUSY output) + +The display can be powered from 3.3V or from **GPIO pin**. See *EPDspi.h* for configuration options. + +**If you want to use different pins, change them in** *EPDspi.h* + +Using *make menuconfig* **select tick rate 1000** ( → Component config → FreeRTOS → Tick rate (Hz) ) to get more accurate timings + +--- + +#### How to build + +Configure your esp32 build environment as for **esp-idf examples** + +Clone the repository + +`git clone https://github.com/loboris/ESP32_ePaper_example.git` + +Execute menuconfig and configure your Serial flash config and other settings. Included *sdkconfig.defaults* sets some defaults to be used. + +Navigate to **ePaper Display DEMO Configuration** and set **SPIFFS** options. + +Select if you want to use **wifi** (recommended) to get the time from **NTP** server and set your WiFi SSID and password. + +`make menuconfig` + +Make and flash the example. + +`make all && make flash` + +--- + +#### Prepare **SPIFFS** image + +*The demo uses some image and font files and it is necessary to flash the spiffs image* + +**To flash already prepared image** *components/spiffs_image/spiffs_image.img* execute: + +`make copyfs` + +--- + +You can also prepare different SFPIFFS **image** and flash it to ESP32. + +Files to be included on spiffs are already in **components/spiffs_image/image/** directory. You can add or remove the files you want to include. + +Then execute: + +`make makefs` + +to create **spiffs image** in *build* directory **without flashing** to ESP32 + +Or execute: + +`make flashfs` + +to create **spiffs image** in *build* directory and **flash** it to ESP32 + +--- + +Tested on Waveshare 2.9" ePaper module connected to SparkFun ESP32 Thing board. +![Tested on](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/2.9inch-e-paper-module-4.jpg) + +--- + +![Fonts](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/EPD-fonts.jpg) +![Rotated](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/EPD-Rotated.jpg) +![7-segFont](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/EPD-7sef_font.jpg) +![Grayscale](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/EPD_Grayscale.jpg) +![No power](https://raw.githubusercontent.com/loboris/ESP32_ePaper_example/master/Documents/EPD-No_power.jpg) diff --git a/components/epaper/DefaultFont.c b/components/epaper/DefaultFont.c new file mode 100644 index 0000000..3e3c2a0 --- /dev/null +++ b/components/epaper/DefaultFont.c @@ -0,0 +1,333 @@ +// Default font + +// ======================================================================== +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// Header Format: +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset (start Y of visible pixels) +// Width (width of the visible pixels) +// Height (height of the visible pixels) +// xOffset (start X of visible pixels) +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. +// ======================================================================== + +// dejavu +// Point Size : 12 +// Memory usage : 1158 bytes +// # characters : 95 + +const unsigned char tft_DefaultFont[] = +{ +0x00, 0x0B, 0x86, 0x04, + +// ' ' +0x20,0x0A,0x00,0x00,0x00,0x04, + +// '!' +0x21,0x01,0x01,0x09,0x02,0x05, +0xFD,0x80, +// '"' +0x22,0x01,0x03,0x03,0x01,0x05, +0xB6,0x80, +// '#' +0x23,0x02,0x08,0x08,0x01,0x0A, +0x12,0x14,0x7F,0x24,0x24,0xFE,0x28,0x48, +// '$' +0x24,0x01,0x06,0x0B,0x02,0x08, +0x21,0xCA,0xA8,0xE0,0xE2,0xAA,0x70,0x82,0x00, +// '%' +0x25,0x01,0x0A,0x09,0x00,0x0B, +0x61,0x24,0x89,0x22,0x50,0x6D,0x82,0x91,0x24,0x49,0x21,0x80, +// '&' +0x26,0x01,0x09,0x09,0x01,0x0A, +0x30,0x24,0x10,0x0C,0x05,0x14,0x4A,0x19,0x8C,0x7B,0x00, +// ''' +0x27,0x01,0x01,0x03,0x01,0x03, +0xE0, +// '(' +0x28,0x00,0x03,0x0B,0x01,0x05, +0x69,0x49,0x24,0x48,0x80, +// ')' +0x29,0x00,0x03,0x0B,0x01,0x05, +0x89,0x12,0x49,0x4A,0x00, +// '*' +0x2A,0x01,0x05,0x06,0x01,0x06, +0x25,0x5C,0xEA,0x90, +// '+' +0x2B,0x03,0x07,0x07,0x01,0x0A, +0x10,0x20,0x47,0xF1,0x02,0x04,0x00, +// ',' +0x2C,0x08,0x01,0x03,0x01,0x04, +0xE0, +// '-' +0x2D,0x06,0x03,0x01,0x01,0x04, +0xE0, +// '.' +0x2E,0x08,0x01,0x02,0x01,0x04, +0xC0, +// '/' +0x2F,0x01,0x04,0x0A,0x00,0x04, +0x11,0x22,0x24,0x44,0x88, +// '0' +0x30,0x01,0x06,0x09,0x01,0x08, +0x79,0x28,0x61,0x86,0x18,0x52,0x78, +// '1' +0x31,0x01,0x05,0x09,0x01,0x08, +0xE1,0x08,0x42,0x10,0x84,0xF8, +// '2' +0x32,0x01,0x07,0x09,0x01,0x08, +0x79,0x18,0x10,0x20,0x82,0x08,0x20,0xFC, +// '3' +0x33,0x01,0x06,0x09,0x01,0x08, +0x7A,0x10,0x41,0x38,0x30,0x63,0x78, +// '4' +0x34,0x01,0x06,0x09,0x01,0x08, +0x18,0x62,0x92,0x4A,0x2F,0xC2,0x08, +// '5' +0x35,0x01,0x06,0x09,0x01,0x08, +0xFA,0x08,0x3C,0x0C,0x10,0x63,0x78, +// '6' +0x36,0x01,0x06,0x09,0x01,0x08, +0x39,0x18,0x3E,0xCE,0x18,0x53,0x78, +// '7' +0x37,0x01,0x06,0x09,0x01,0x08, +0xFC,0x10,0x82,0x10,0x42,0x08,0x40, +// '8' +0x38,0x01,0x06,0x09,0x01,0x08, +0x7B,0x38,0x73,0x7B,0x38,0x73,0x78, +// '9' +0x39,0x01,0x06,0x09,0x01,0x08, +0x7B,0x28,0x61,0xCD,0xD0,0x62,0x70, +// ':' +0x3A,0x04,0x01,0x06,0x01,0x04, +0xCC, +// ';' +0x3B,0x04,0x01,0x07,0x01,0x04, +0xCE, +// '<' +0x3C,0x03,0x08,0x06,0x01,0x0A, +0x03,0x1E,0xE0,0xE0,0x1E,0x03, +// '=' +0x3D,0x05,0x08,0x03,0x01,0x0A, +0xFF,0x00,0xFF, +// '>' +0x3E,0x03,0x08,0x06,0x01,0x0A, +0xC0,0x78,0x07,0x07,0x78,0xC0, +// '?' +0x3F,0x01,0x05,0x09,0x00,0x06, +0x74,0x42,0x22,0x10,0x04,0x20, +// '@' +0x40,0x01,0x0B,0x0B,0x01,0x0D, +0x1F,0x06,0x19,0x01,0x46,0x99,0x13,0x22,0x64,0x54,0x6C,0x40,0x04,0x10,0x7C,0x00, +// 'A' +0x41,0x01,0x08,0x09,0x00,0x08, +0x18,0x18,0x24,0x24,0x24,0x42,0x7E,0x42,0x81, +// 'B' +0x42,0x01,0x06,0x09,0x01,0x08, +0xFA,0x18,0x61,0xFA,0x18,0x61,0xF8, +// 'C' +0x43,0x01,0x06,0x09,0x01,0x08, +0x39,0x18,0x20,0x82,0x08,0x11,0x38, +// 'D' +0x44,0x01,0x07,0x09,0x01,0x09, +0xF9,0x0A,0x0C,0x18,0x30,0x60,0xC2,0xF8, +// 'E' +0x45,0x01,0x06,0x09,0x01,0x08, +0xFE,0x08,0x20,0xFE,0x08,0x20,0xFC, +// 'F' +0x46,0x01,0x05,0x09,0x01,0x07, +0xFC,0x21,0x0F,0xC2,0x10,0x80, +// 'G' +0x47,0x01,0x07,0x09,0x01,0x09, +0x3C,0x86,0x04,0x08,0xF0,0x60,0xA1,0x3C, +// 'H' +0x48,0x01,0x07,0x09,0x01,0x09, +0x83,0x06,0x0C,0x1F,0xF0,0x60,0xC1,0x82, +// 'I' +0x49,0x01,0x01,0x09,0x01,0x03, +0xFF,0x80, +// 'J' +0x4A,0x01,0x03,0x0B,0xFF,0x03, +0x24,0x92,0x49,0x27,0x00, +// 'K' +0x4B,0x01,0x07,0x09,0x01,0x07, +0x85,0x12,0x45,0x0C,0x14,0x24,0x44,0x84, +// 'L' +0x4C,0x01,0x05,0x09,0x01,0x06, +0x84,0x21,0x08,0x42,0x10,0xF8, +// 'M' +0x4D,0x01,0x08,0x09,0x01,0x0A, +0x81,0xC3,0xC3,0xA5,0xA5,0x99,0x99,0x81,0x81, +// 'N' +0x4E,0x01,0x07,0x09,0x01,0x09, +0xC3,0x86,0x8D,0x19,0x31,0x62,0xC3,0x86, +// 'O' +0x4F,0x01,0x07,0x09,0x01,0x09, +0x38,0x8A,0x0C,0x18,0x30,0x60,0xA2,0x38, +// 'P' +0x50,0x01,0x06,0x09,0x01,0x08, +0xFA,0x38,0x63,0xFA,0x08,0x20,0x80, +// 'Q' +0x51,0x01,0x07,0x0B,0x01,0x09, +0x38,0x8A,0x0C,0x18,0x30,0x60,0xA2,0x38,0x10,0x10, +// 'R' +0x52,0x01,0x07,0x09,0x01,0x08, +0xF9,0x1A,0x14,0x6F,0x91,0x21,0x42,0x82, +// 'S' +0x53,0x01,0x06,0x09,0x01,0x08, +0x7B,0x18,0x30,0x78,0x30,0x63,0x78, +// 'T' +0x54,0x01,0x07,0x09,0x00,0x07, +0xFE,0x20,0x40,0x81,0x02,0x04,0x08,0x10, +// 'U' +0x55,0x01,0x07,0x09,0x01,0x09, +0x83,0x06,0x0C,0x18,0x30,0x60,0xA2,0x38, +// 'V' +0x56,0x01,0x0A,0x09,0xFF,0x08, +0x40,0x90,0x22,0x10,0x84,0x21,0x04,0x81,0x20,0x30,0x0C,0x00, +// 'W' +0x57,0x01,0x0B,0x09,0x00,0x0B, +0x84,0x28,0x89,0x11,0x27,0x22,0xA8,0x55,0x0E,0xE0,0x88,0x11,0x00, +// 'X' +0x58,0x01,0x07,0x09,0x00,0x07, +0xC6,0x88,0xA1,0xC1,0x07,0x0A,0x22,0x82, +// 'Y' +0x59,0x01,0x07,0x09,0x00,0x07, +0x82,0x89,0x11,0x43,0x82,0x04,0x08,0x10, +// 'Z' +0x5A,0x01,0x07,0x09,0x01,0x09, +0xFE,0x04,0x10,0x41,0x04,0x10,0x40,0xFE, +// '[' +0x5B,0x01,0x02,0x0B,0x02,0x05, +0xEA,0xAA,0xAC, +// '\' +0x5C,0x01,0x04,0x0A,0x00,0x04, +0x88,0x44,0x42,0x22,0x11, +// ']' +0x5D,0x01,0x02,0x0B,0x01,0x05, +0xD5,0x55,0x5C, +// '^' +0x5E,0x01,0x08,0x03,0x01,0x0A, +0x18,0x24,0x42, +// '_' +0x5F,0x0C,0x06,0x01,0x00,0x06, +0xFC, +// '`' +0x60,0x00,0x03,0x02,0x01,0x06, +0x44, +// 'a' +0x61,0x03,0x06,0x07,0x01,0x08, +0x7A,0x30,0x5F,0x86,0x37,0x40, +// 'b' +0x62,0x00,0x06,0x0A,0x01,0x08, +0x82,0x08,0x2E,0xCA,0x18,0x61,0xCE,0xE0, +// 'c' +0x63,0x03,0x05,0x07,0x01,0x07, +0x72,0x61,0x08,0x25,0xC0, +// 'd' +0x64,0x00,0x06,0x0A,0x01,0x08, +0x04,0x10,0x5D,0xCE,0x18,0x61,0xCD,0xD0, +// 'e' +0x65,0x03,0x06,0x07,0x01,0x08, +0x39,0x38,0x7F,0x81,0x13,0x80, +// 'f' +0x66,0x00,0x04,0x0A,0x00,0x04, +0x34,0x4F,0x44,0x44,0x44, +// 'g' +0x67,0x03,0x06,0x0A,0x01,0x08, +0x77,0x38,0x61,0x87,0x37,0x41,0x4C,0xE0, +// 'h' +0x68,0x00,0x06,0x0A,0x01,0x08, +0x82,0x08,0x2E,0xC6,0x18,0x61,0x86,0x10, +// 'i' +0x69,0x01,0x01,0x09,0x01,0x03, +0xBF,0x80, +// 'j' +0x6A,0x01,0x02,0x0C,0x00,0x03, +0x45,0x55,0x56, +// 'k' +0x6B,0x00,0x06,0x0A,0x01,0x07, +0x82,0x08,0x22,0x92,0x8E,0x28,0x92,0x20, +// 'l' +0x6C,0x00,0x01,0x0A,0x01,0x03, +0xFF,0xC0, +// 'm' +0x6D,0x03,0x09,0x07,0x01,0x0B, +0xB3,0x66,0x62,0x31,0x18,0x8C,0x46,0x22, +// 'n' +0x6E,0x03,0x06,0x07,0x01,0x08, +0xBB,0x18,0x61,0x86,0x18,0x40, +// 'o' +0x6F,0x03,0x06,0x07,0x01,0x08, +0x7B,0x38,0x61,0x87,0x37,0x80, +// 'p' +0x70,0x03,0x06,0x0A,0x01,0x08, +0xBB,0x28,0x61,0x87,0x3B,0xA0,0x82,0x00, +// 'q' +0x71,0x03,0x06,0x0A,0x01,0x08, +0x77,0x38,0x61,0x87,0x37,0x41,0x04,0x10, +// 'r' +0x72,0x03,0x04,0x07,0x01,0x05, +0xBC,0x88,0x88,0x80, +// 's' +0x73,0x03,0x06,0x07,0x01,0x07, +0x72,0x28,0x1C,0x0A,0x27,0x00, +// 't' +0x74,0x01,0x04,0x09,0x00,0x05, +0x44,0xF4,0x44,0x44,0x30, +// 'u' +0x75,0x03,0x06,0x07,0x01,0x08, +0x86,0x18,0x61,0x86,0x37,0x40, +// 'v' +0x76,0x03,0x08,0x07,0xFF,0x06, +0x42,0x42,0x24,0x24,0x24,0x18,0x18, +// 'w' +0x77,0x03,0x09,0x07,0x00,0x09, +0x88,0xC4,0x57,0x4A,0xA5,0x51,0x10,0x88, +// 'x' +0x78,0x03,0x06,0x07,0x00,0x06, +0x85,0x24,0x8C,0x49,0x28,0x40, +// 'y' +0x79,0x03,0x08,0x0A,0xFF,0x06, +0x42,0x42,0x24,0x24,0x14,0x18,0x08,0x08,0x10,0x60, +// 'z' +0x7A,0x03,0x05,0x07,0x00,0x05, +0xF8,0x44,0x44,0x43,0xE0, +// '{' +0x7B,0x01,0x05,0x0B,0x02,0x08, +0x19,0x08,0x42,0x60,0x84,0x21,0x06, +// '|' +0x7C,0x01,0x01,0x0C,0x02,0x04, +0xFF,0xF0, +// '}' +0x7D,0x01,0x05,0x0B,0x01,0x08, +0xC1,0x08,0x42,0x0C,0x84,0x21,0x30, +// '~' +0x7E,0x04,0x08,0x03,0x01,0x0A, +0x00,0x71,0x8E, + +// Terminator +0xFF +}; diff --git a/components/epaper/DejaVuSans18.c b/components/epaper/DejaVuSans18.c new file mode 100644 index 0000000..fe53372 --- /dev/null +++ b/components/epaper/DejaVuSans18.c @@ -0,0 +1,322 @@ +// ============================================================================ +// Proportional font Header Format: +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. +// ============================================================================ + +// DejaVuSans +// Point Size : 18 +// Memory usage : 1828 bytes +// # characters : 95 + +const unsigned char tft_Dejavu18x[] = +{ +0x00, 0x12, 0x00, 0x00, + +// ' ' +0x20,0x0E,0x00,0x00,0x00,0x06, + +// '!' +0x21,0x01,0x02,0x0D,0x03,0x07, +0xFF,0xFF,0xC3,0xC0, +// '"' +0x22,0x01,0x06,0x05,0x01,0x08, +0xCF,0x3C,0xF3,0xCC, +// '#' +0x23,0x00,0x0C,0x0E,0x01,0x0F, +0x04,0x40,0x44,0x0C,0xC0,0xC8,0x7F,0xF7,0xFF,0x09,0x81,0x90,0xFF,0xEF,0xFE,0x13,0x03,0x30,0x32,0x02,0x20, +// '$' +0x24,0x00,0x0A,0x11,0x01,0x0B, +0x08,0x02,0x03,0xE1,0xFC,0xE9,0x32,0x0F,0x81,0xF8,0x0F,0x02,0x60,0x9A,0x2E,0xFF,0x1F,0x80,0x80,0x20,0x08,0x00, +// '%' +0x25,0x01,0x0F,0x0D,0x01,0x11, +0x78,0x10,0x90,0x43,0x31,0x86,0x62,0x0C,0xC8,0x19,0x10,0x1E,0x4F,0x01,0x12,0x02,0x66,0x08,0xCC,0x31,0x98,0x41,0x21,0x03,0xC0, +// '&' +0x26,0x01,0x0C,0x0D,0x01,0x0D, +0x0F,0x01,0xF8,0x30,0x83,0x00,0x38,0x03,0xC0,0x7E,0x6C,0x76,0xC3,0xCC,0x18,0xE1,0xC7,0xFE,0x3E,0x70, +// ''' +0x27,0x01,0x02,0x05,0x01,0x04, +0xFF,0xC0, +// '(' +0x28,0x00,0x04,0x10,0x02,0x07, +0x32,0x66,0x4C,0xCC,0xCC,0xC4,0x66,0x23, +// ')' +0x29,0x00,0x04,0x10,0x01,0x07, +0xC4,0x66,0x23,0x33,0x33,0x32,0x66,0x4C, +// '*' +0x2A,0x01,0x07,0x08,0x01,0x09, +0x11,0x25,0x51,0xC3,0x8A,0xA4,0x88, +// '+' +0x2B,0x02,0x0C,0x0C,0x02,0x0F, +0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x0F,0xFF,0xFF,0xF0,0x60,0x06,0x00,0x60,0x06,0x00,0x60, +// ',' +0x2C,0x0C,0x03,0x04,0x01,0x06, +0x6D,0x40, +// '-' +0x2D,0x08,0x05,0x02,0x01,0x07, +0xFF,0xC0, +// '.' +0x2E,0x0C,0x02,0x02,0x02,0x06, +0xF0, +// '/' +0x2F,0x01,0x06,0x0F,0x00,0x06, +0x0C,0x31,0x86,0x18,0xE3,0x0C,0x31,0xC6,0x18,0x63,0x0C,0x00, +// '0' +0x30,0x01,0x09,0x0D,0x01,0x0B, +0x3E,0x3F,0x98,0xD8,0x3C,0x1E,0x0F,0x07,0x83,0xC1,0xE0,0xD8,0xCF,0xE3,0xE0, +// '1' +0x31,0x01,0x08,0x0D,0x02,0x0B, +0x38,0xF8,0xD8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF, +// '2' +0x32,0x01,0x09,0x0D,0x01,0x0B, +0x7C,0x7F,0x21,0xC0,0x60,0x30,0x30,0x18,0x18,0x18,0x18,0x18,0x1F,0xEF,0xF0, +// '3' +0x33,0x01,0x09,0x0D,0x01,0x0B, +0x7E,0x7F,0xA0,0xE0,0x30,0x39,0xF0,0xFC,0x07,0x01,0x80,0xE0,0xFF,0xE7,0xE0, +// '4' +0x34,0x01,0x0A,0x0D,0x01,0x0B, +0x07,0x01,0xC0,0xB0,0x6C,0x13,0x08,0xC6,0x31,0x0C,0xFF,0xFF,0xF0,0x30,0x0C,0x03,0x00, +// '5' +0x35,0x01,0x08,0x0D,0x01,0x0B, +0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x47,0x03,0x03,0x03,0x87,0xFE,0x7C, +// '6' +0x36,0x01,0x09,0x0D,0x01,0x0B, +0x1E,0x1F,0x9C,0x5C,0x0C,0x06,0xF3,0xFD,0xC7,0xC1,0xE0,0xD8,0xEF,0xE1,0xE0, +// '7' +0x37,0x01,0x08,0x0D,0x01,0x0B, +0xFF,0xFF,0x06,0x06,0x06,0x0E,0x0C,0x0C,0x1C,0x18,0x18,0x38,0x30, +// '8' +0x38,0x01,0x09,0x0D,0x01,0x0B, +0x3E,0x3F,0xB8,0xF8,0x3E,0x39,0xF1,0xFD,0xC7,0xC1,0xE0,0xF8,0xEF,0xE3,0xE0, +// '9' +0x39,0x01,0x09,0x0D,0x01,0x0B, +0x3C,0x3F,0xB8,0xD8,0x3C,0x1F,0x1D,0xFE,0x7B,0x01,0x81,0xD1,0xCF,0xC3,0xC0, +// ':' +0x3A,0x05,0x02,0x09,0x02,0x06, +0xF0,0x03,0xC0, +// ';' +0x3B,0x05,0x03,0x0B,0x01,0x06, +0x6C,0x00,0x03,0x6A,0x00, +// '<' +0x3C,0x04,0x0B,0x0A,0x02,0x0F, +0x00,0x20,0x3C,0x1F,0x1F,0x0F,0x81,0xF0,0x0F,0x80,0x3E,0x01,0xE0,0x04, +// '=' +0x3D,0x05,0x0B,0x06,0x02,0x0F, +0xFF,0xFF,0xFC,0x00,0x00,0x0F,0xFF,0xFF,0xC0, +// '>' +0x3E,0x04,0x0B,0x0A,0x02,0x0F, +0x80,0x1E,0x01,0xF0,0x07,0xC0,0x3E,0x07,0xC3,0xE3,0xE0,0xF0,0x10,0x00, +// '?' +0x3F,0x01,0x07,0x0D,0x01,0x0A, +0x79,0xFA,0x38,0x30,0x61,0x86,0x18,0x30,0x60,0x01,0x83,0x00, +// '@' +0x40,0x01,0x10,0x10,0x01,0x12, +0x07,0xE0,0x1F,0xF8,0x3C,0x1C,0x70,0x06,0x60,0x07,0xE3,0x63,0xC7,0xE3,0xC6,0x63,0xC6,0x66,0xC7,0xFC,0xE3,0x70,0x60,0x00,0x70,0x00,0x3C,0x30,0x1F,0xF0,0x07,0xC0, +// 'A' +0x41,0x01,0x0C,0x0D,0x00,0x0C, +0x06,0x00,0x60,0x0F,0x00,0xF0,0x19,0x81,0x98,0x19,0x83,0x0C,0x3F,0xC7,0xFE,0x60,0x66,0x06,0xC0,0x30, +// 'B' +0x42,0x01,0x09,0x0D,0x02,0x0C, +0xFC,0x7F,0xB0,0xD8,0x6C,0x37,0xF3,0xF9,0x86,0xC1,0xE0,0xF0,0xFF,0xEF,0xE0, +// 'C' +0x43,0x01,0x0B,0x0D,0x01,0x0D, +0x0F,0xC7,0xFD,0xC0,0xB0,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x0C,0x01,0xC0,0x9F,0xF0,0xFC, +// 'D' +0x44,0x01,0x0B,0x0D,0x02,0x0E, +0xFE,0x1F,0xF3,0x07,0x60,0x6C,0x07,0x80,0xF0,0x1E,0x03,0xC0,0x78,0x1B,0x07,0x7F,0xCF,0xE0, +// 'E' +0x45,0x01,0x08,0x0D,0x02,0x0B, +0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// 'F' +0x46,0x01,0x08,0x0D,0x02,0x0A, +0xFF,0xFF,0xC0,0xC0,0xC0,0xFE,0xFE,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, +// 'G' +0x47,0x01,0x0B,0x0D,0x01,0x0E, +0x0F,0xC7,0xFD,0xC0,0xB0,0x0C,0x01,0x87,0xF0,0xFE,0x03,0xC0,0x6C,0x0D,0xC1,0x9F,0xE0,0xF8, +// 'H' +0x48,0x01,0x0A,0x0D,0x02,0x0E, +0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xFF,0xFF,0xFF,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xC0, +// 'I' +0x49,0x01,0x02,0x0D,0x02,0x06, +0xFF,0xFF,0xFF,0xC0, +// 'J' +0x4A,0x01,0x05,0x11,0xFF,0x06, +0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0xFE,0xE0, +// 'K' +0x4B,0x01,0x0B,0x0D,0x02,0x0C, +0xC1,0x98,0x63,0x18,0x66,0x0D,0x81,0xE0,0x3C,0x06,0xC0,0xCC,0x18,0xC3,0x0C,0x60,0xCC,0x0C, +// 'L' +0x4C,0x01,0x08,0x0D,0x02,0x0A, +0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// 'M' +0x4D,0x01,0x0C,0x0D,0x02,0x10, +0xE0,0x7F,0x0F,0xF0,0xFD,0x8B,0xD9,0xBD,0x9B,0xCF,0x3C,0xF3,0xC6,0x3C,0x63,0xC0,0x3C,0x03,0xC0,0x30, +// 'N' +0x4E,0x01,0x0A,0x0D,0x02,0x0E, +0xE0,0xF8,0x3F,0x0F,0xC3,0xD8,0xF6,0x3C,0xCF,0x1B,0xC6,0xF0,0xFC,0x3F,0x07,0xC1,0xC0, +// 'O' +0x4F,0x01,0x0C,0x0D,0x01,0x0E, +0x1F,0x83,0xFC,0x70,0xE6,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x70,0xE3,0xFC,0x1F,0x80, +// 'P' +0x50,0x01,0x08,0x0D,0x02,0x0B, +0xFC,0xFE,0xC7,0xC3,0xC3,0xC7,0xFE,0xFC,0xC0,0xC0,0xC0,0xC0,0xC0, +// 'Q' +0x51,0x01,0x0C,0x0F,0x01,0x0E, +0x1F,0x83,0xFC,0x70,0xE6,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x70,0xE3,0xFC,0x1F,0x80,0x18,0x00,0xC0, +// 'R' +0x52,0x01,0x0A,0x0D,0x02,0x0D, +0xFC,0x3F,0x8C,0x73,0x0C,0xC3,0x31,0xCF,0xE3,0xF0,0xC6,0x30,0xCC,0x33,0x06,0xC1,0xC0, +// 'S' +0x53,0x01,0x0A,0x0D,0x01,0x0B, +0x3E,0x1F,0xCE,0x13,0x00,0xC0,0x1F,0x03,0xF0,0x0E,0x01,0x80,0x68,0x3B,0xFC,0x7E,0x00, +// 'T' +0x54,0x01,0x0C,0x0D,0x00,0x0C, +0xFF,0xFF,0xFF,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00, +// 'U' +0x55,0x01,0x0A,0x0D,0x02,0x0E, +0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x36,0x19,0xFE,0x1E,0x00, +// 'V' +0x56,0x01,0x0C,0x0D,0x00,0x0C, +0xC0,0x36,0x06,0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x19,0x80,0xF0,0x0F,0x00,0x60,0x06,0x00, +// 'W' +0x57,0x01,0x11,0x0D,0x01,0x13, +0xC1,0xC1,0xE0,0xE0,0xD8,0xF8,0xCC,0x6C,0x66,0x36,0x33,0x1B,0x18,0xD8,0xD8,0x6C,0x6C,0x36,0x36,0x1F,0x1F,0x07,0x07,0x03,0x83,0x81,0xC1,0xC0, +// 'X' +0x58,0x01,0x0B,0x0D,0x01,0x0D, +0x70,0xE6,0x18,0xE6,0x0D,0xC0,0xF0,0x1C,0x03,0x80,0x78,0x1B,0x07,0x30,0xC7,0x30,0x6E,0x0E, +// 'Y' +0x59,0x01,0x0C,0x0D,0x00,0x0C, +0xE0,0x76,0x06,0x30,0xC1,0x98,0x19,0x80,0xF0,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00, +// 'Z' +0x5A,0x01,0x0B,0x0D,0x01,0x0D, +0xFF,0xFF,0xFC,0x07,0x01,0xC0,0x30,0x0E,0x03,0x80,0xE0,0x18,0x06,0x01,0xC0,0x7F,0xFF,0xFE, +// '[' +0x5B,0x00,0x04,0x10,0x01,0x07, +0xFF,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xFF, +// '\' +0x5C,0x01,0x06,0x0F,0x00,0x06, +0xC3,0x06,0x18,0x61,0xC3,0x0C,0x30,0xE1,0x86,0x18,0x30,0xC0, +// ']' +0x5D,0x00,0x04,0x10,0x02,0x07, +0xFF,0x33,0x33,0x33,0x33,0x33,0x33,0xFF, +// '^' +0x5E,0x01,0x0B,0x05,0x02,0x0F, +0x0E,0x03,0xE0,0xC6,0x30,0x6C,0x06, +// '_' +0x5F,0x10,0x09,0x02,0x00,0x09, +0xFF,0xFF,0xC0, +// '`' +0x60,0x00,0x04,0x03,0x02,0x09, +0xC6,0x30, +// 'a' +0x61,0x04,0x08,0x0A,0x01,0x0A, +0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// 'b' +0x62,0x00,0x09,0x0E,0x02,0x0B, +0xC0,0x60,0x30,0x18,0x0D,0xE7,0xFB,0x8F,0x83,0xC1,0xE0,0xF0,0x7C,0x7F,0xF6,0xF0, +// 'c' +0x63,0x04,0x08,0x0A,0x01,0x09, +0x1E,0x7F,0x61,0xC0,0xC0,0xC0,0xC0,0x61,0x7F,0x1E, +// 'd' +0x64,0x00,0x09,0x0E,0x01,0x0B, +0x01,0x80,0xC0,0x60,0x33,0xDB,0xFF,0x8F,0x83,0xC1,0xE0,0xF0,0x7C,0x77,0xF9,0xEC, +// 'e' +0x65,0x04,0x0A,0x0A,0x01,0x0B, +0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// 'f' +0x66,0x00,0x07,0x0E,0x00,0x06, +0x1E,0x7C,0xC1,0x8F,0xFF,0xCC,0x18,0x30,0x60,0xC1,0x83,0x06,0x00, +// 'g' +0x67,0x04,0x09,0x0E,0x01,0x0B, +0x3D,0xBF,0xF8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0x7F,0x9E,0xC0,0x68,0x67,0xF1,0xF0, +// 'h' +0x68,0x00,0x08,0x0E,0x02,0x0B, +0xC0,0xC0,0xC0,0xC0,0xDE,0xFE,0xE7,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +// 'i' +0x69,0x00,0x02,0x0E,0x02,0x05, +0xF0,0xFF,0xFF,0xF0, +// 'j' +0x6A,0x00,0x04,0x12,0x00,0x05, +0x33,0x00,0x33,0x33,0x33,0x33,0x33,0x33,0xEC, +// 'k' +0x6B,0x00,0x09,0x0E,0x02,0x0A, +0xC0,0x60,0x30,0x18,0x0C,0x36,0x33,0x31,0xB0,0xF0,0x78,0x36,0x19,0x8C,0x66,0x18, +// 'l' +0x6C,0x00,0x02,0x0E,0x02,0x05, +0xFF,0xFF,0xFF,0xF0, +// 'm' +0x6D,0x04,0x0E,0x0A,0x02,0x11, +0xDC,0x7B,0xFB,0xEE,0x79,0xF0,0xC3,0xC3,0x0F,0x0C,0x3C,0x30,0xF0,0xC3,0xC3,0x0F,0x0C,0x30, +// 'n' +0x6E,0x04,0x08,0x0A,0x02,0x0B, +0xDE,0xFE,0xE7,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +// 'o' +0x6F,0x04,0x0A,0x0A,0x01,0x0B, +0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// 'p' +0x70,0x04,0x09,0x0E,0x02,0x0B, +0xDE,0x7F,0xB8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0xFF,0x6F,0x30,0x18,0x0C,0x06,0x00, +// 'q' +0x71,0x04,0x09,0x0E,0x01,0x0B, +0x3D,0xBF,0xF8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0x7F,0x9E,0xC0,0x60,0x30,0x18,0x0C, +// 'r' +0x72,0x04,0x06,0x0A,0x02,0x08, +0xDF,0xFE,0x30,0xC3,0x0C,0x30,0xC3,0x00, +// 's' +0x73,0x04,0x08,0x0A,0x01,0x08, +0x7C,0xFE,0xC2,0xE0,0x7C,0x1E,0x06,0x86,0xFE,0x78, +// 't' +0x74,0x01,0x06,0x0D,0x01,0x07, +0x61,0x86,0x3F,0xFD,0x86,0x18,0x61,0x86,0x1F,0x3C, +// 'u' +0x75,0x04,0x08,0x0A,0x02,0x0B, +0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// 'v' +0x76,0x04,0x0C,0x0A,0x00,0x0B, +0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x19,0x80,0xF0,0x0F,0x00,0x60, +// 'w' +0x77,0x04,0x0F,0x0A,0x01,0x10, +0x63,0x8C,0xC7,0x19,0x8E,0x31,0xB6,0xC3,0x6D,0x86,0xDB,0x0F,0x1E,0x0E,0x38,0x1C,0x70,0x38,0xE0, +// 'x' +0x78,0x04,0x0A,0x0A,0x01,0x0B, +0xE1,0xD8,0x63,0x30,0xCC,0x1E,0x07,0x83,0x30,0xCC,0x61,0xB8,0x70, +// 'y' +0x79,0x04,0x0C,0x0E,0x00,0x0B, +0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x0F,0x00,0xF0,0x06,0x00,0x60,0x06,0x00,0xC0,0x3C,0x03,0x80, +// 'z' +0x7A,0x04,0x08,0x0A,0x01,0x09, +0xFF,0xFF,0x06,0x0C,0x1C,0x38,0x30,0x70,0xFF,0xFF, +// '{' +0x7B,0x00,0x08,0x11,0x02,0x0B, +0x0F,0x1F,0x18,0x18,0x18,0x18,0x38,0xF0,0xF0,0x38,0x18,0x18,0x18,0x18,0x18,0x1F,0x0F, +// '|' +0x7C,0x00,0x02,0x12,0x02,0x06, +0xFF,0xFF,0xFF,0xFF,0xF0, +// '}' +0x7D,0x00,0x08,0x11,0x02,0x0B, +0xF0,0xF8,0x18,0x18,0x18,0x18,0x1C,0x0F,0x0F,0x1C,0x18,0x18,0x18,0x18,0x18,0xF8,0xF0, +// '~' +0x7E,0x05,0x0B,0x05,0x02,0x0F, +0x00,0x0F,0x87,0xFF,0xC3,0xE0,0x00, + +// Terminator +0xFF +}; diff --git a/components/epaper/DejaVuSans24.c b/components/epaper/DejaVuSans24.c new file mode 100644 index 0000000..aa7e2f1 --- /dev/null +++ b/components/epaper/DejaVuSans24.c @@ -0,0 +1,331 @@ +// ======================================================================== +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// Header Format: +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. +// ======================================================================== + +// dejavu +// Point Size : 24 +// Memory usage : 2724 bytes +// # characters : 95 + +const unsigned char tft_Dejavu24[] = +{ +0x00, 0x17, 0x00, 0x00, + +// ' ' +0x20,0x13,0x00,0x00,0x00,0x08, + +// '!' +0x21,0x01,0x02,0x12,0x04,0x0A, +0xFF,0xFF,0xFF,0x03,0xF0, +// '"' +0x22,0x01,0x06,0x07,0x02,0x0B, +0xCF,0x3C,0xF3,0xCF,0x3C,0xC0, +// '#' +0x23,0x01,0x10,0x12,0x02,0x14, +0x03,0x08,0x03,0x18,0x03,0x18,0x03,0x18,0x02,0x18,0x7F,0xFF,0x7F,0xFF,0x06,0x30,0x04,0x30,0x0C,0x20,0x0C,0x60,0xFF,0xFE,0xFF,0xFE,0x18,0x40,0x18,0xC0,0x18,0xC0,0x18,0xC0,0x10,0xC0, +// '$' +0x24,0x01,0x0B,0x16,0x02,0x0F, +0x04,0x00,0x80,0x10,0x0F,0xC7,0xFD,0xC8,0xB1,0x06,0x20,0xE4,0x0F,0x80,0xFE,0x03,0xE0,0x4E,0x08,0xC1,0x1E,0x27,0xFF,0xC7,0xE0,0x10,0x02,0x00,0x40,0x08,0x00, +// '%' +0x25,0x01,0x14,0x12,0x01,0x17, +0x3C,0x03,0x06,0x60,0x60,0xC3,0x06,0x0C,0x30,0xC0,0xC3,0x1C,0x0C,0x31,0x80,0xC3,0x38,0x0C,0x33,0x00,0x66,0x63,0xC3,0xC6,0x66,0x00,0xCC,0x30,0x1C,0xC3,0x01,0x8C,0x30,0x38,0xC3,0x03,0x0C,0x30,0x60,0xC3,0x06,0x06,0x60,0xC0,0x3C, +// '&' +0x26,0x01,0x10,0x12,0x01,0x13, +0x07,0xC0,0x1F,0xE0,0x38,0x20,0x30,0x00,0x30,0x00,0x30,0x00,0x18,0x00,0x1C,0x00,0x3E,0x00,0x77,0x06,0xE3,0x86,0xC1,0xCC,0xC0,0xFC,0xC0,0x78,0xE0,0x78,0x70,0xFC,0x3F,0xCE,0x0F,0x87, +// ''' +0x27,0x01,0x02,0x07,0x02,0x07, +0xFF,0xFC, +// '(' +0x28,0x01,0x05,0x15,0x02,0x09, +0x19,0x8C,0xC6,0x31,0x18,0xC6,0x31,0x8C,0x61,0x0C,0x63,0x0C,0x61,0x80, +// ')' +0x29,0x01,0x05,0x15,0x02,0x09, +0xC3,0x18,0x63,0x18,0x43,0x18,0xC6,0x31,0x8C,0x46,0x31,0x98,0xCC,0x00, +// '*' +0x2A,0x01,0x0B,0x0A,0x00,0x0C, +0x04,0x00,0x83,0x11,0xBA,0xE1,0xF0,0x3E,0x1D,0x76,0x23,0x04,0x00,0x80, +// '+' +0x2B,0x03,0x10,0x10,0x03,0x14, +0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0xFF,0xFF,0xFF,0xFF,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80, +// ',' +0x2C,0x10,0x03,0x06,0x02,0x08, +0x6D,0xBD,0x80, +// '-' +0x2D,0x0B,0x06,0x02,0x01,0x09, +0xFF,0xF0, +// '.' +0x2E,0x10,0x02,0x03,0x03,0x08, +0xFC, +// '/' +0x2F,0x01,0x08,0x14,0x00,0x08, +0x03,0x07,0x06,0x06,0x06,0x0C,0x0C,0x0C,0x18,0x18,0x18,0x18,0x30,0x30,0x30,0x60,0x60,0x60,0xE0,0xC0, +// '0' +0x30,0x01,0x0C,0x12,0x02,0x0F, +0x0F,0x03,0xFC,0x70,0xE6,0x06,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x60,0x67,0x0E,0x3F,0xC0,0xF0, +// '1' +0x31,0x01,0x0A,0x12,0x03,0x0F, +0x3C,0x3F,0x0C,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0xFF,0xFF,0xF0, +// '2' +0x32,0x01,0x0C,0x12,0x02,0x0F, +0x3F,0x0F,0xF8,0xC1,0xC0,0x0E,0x00,0x60,0x06,0x00,0x60,0x0C,0x01,0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xFF,0xEF,0xFE, +// '3' +0x33,0x01,0x0C,0x12,0x02,0x0F, +0x3F,0x07,0xFC,0x41,0xC0,0x06,0x00,0x60,0x06,0x00,0x60,0x0C,0x1F,0x81,0xFC,0x00,0xE0,0x07,0x00,0x30,0x03,0x00,0x78,0x0E,0xFF,0xC3,0xF0, +// '4' +0x34,0x01,0x0D,0x12,0x01,0x0F, +0x01,0xC0,0x1E,0x00,0xB0,0x0D,0x80,0xCC,0x06,0x60,0x63,0x03,0x18,0x30,0xC3,0x06,0x18,0x31,0x81,0x8F,0xFF,0xFF,0xFC,0x03,0x00,0x18,0x00,0xC0,0x06,0x00, +// '5' +0x35,0x01,0x0B,0x12,0x02,0x0F, +0x7F,0xCF,0xF9,0x80,0x30,0x06,0x00,0xC0,0x1F,0xC3,0xFC,0x41,0xC0,0x1C,0x01,0x80,0x30,0x06,0x00,0xC0,0x3C,0x0E,0xFF,0x8F,0xC0, +// '6' +0x36,0x01,0x0C,0x12,0x02,0x0F, +0x07,0xC1,0xFE,0x38,0x27,0x00,0x60,0x0C,0x00,0xCF,0x8D,0xFC,0xF8,0xEF,0x07,0xE0,0x3E,0x03,0xE0,0x36,0x03,0x70,0x77,0x8E,0x3F,0xC0,0xF8, +// '7' +0x37,0x01,0x0B,0x12,0x02,0x0F, +0xFF,0xFF,0xFC,0x03,0x00,0x60,0x1C,0x03,0x00,0x60,0x18,0x03,0x00,0xE0,0x18,0x03,0x00,0xC0,0x18,0x07,0x00,0xC0,0x18,0x06,0x00, +// '8' +0x38,0x01,0x0C,0x12,0x02,0x0F, +0x1F,0x87,0xFE,0x70,0xEC,0x03,0xC0,0x3C,0x03,0xC0,0x37,0x0E,0x3F,0xC3,0xFC,0x70,0xEC,0x03,0xC0,0x3C,0x03,0xC0,0x37,0x0E,0x7F,0xE1,0xF8, +// '9' +0x39,0x01,0x0C,0x12,0x02,0x0F, +0x1F,0x03,0xFC,0x71,0xCE,0x0E,0xC0,0x6C,0x07,0xC0,0x7C,0x07,0xE0,0xF7,0x1F,0x3F,0xB1,0xF3,0x00,0x30,0x06,0x00,0xE4,0x1C,0x7F,0x83,0xE0, +// ':' +0x3A,0x07,0x02,0x0C,0x03,0x08, +0xFC,0x00,0x3F, +// ';' +0x3B,0x07,0x03,0x0F,0x02,0x08, +0x6D,0x80,0x00,0x0D,0xB7,0xB0, +// '<' +0x3C,0x05,0x0F,0x0D,0x03,0x14, +0x00,0x02,0x00,0x3C,0x03,0xF0,0x3F,0x01,0xF8,0x1F,0x80,0x3C,0x00,0x7E,0x00,0x1F,0x80,0x0F,0xC0,0x03,0xF0,0x00,0xF0,0x00,0x20, +// '=' +0x3D,0x08,0x0F,0x07,0x03,0x14, +0xFF,0xFF,0xFF,0xFC,0x00,0x00,0x00,0x00,0x00,0x1F,0xFF,0xFF,0xFF,0x80, +// '>' +0x3E,0x05,0x0F,0x0D,0x03,0x14, +0x80,0x01,0xE0,0x01,0xF8,0x00,0x7E,0x00,0x3F,0x00,0x0F,0xC0,0x07,0x80,0x3F,0x03,0xF0,0x1F,0x81,0xF8,0x07,0x80,0x08,0x00,0x00, +// '?' +0x3F,0x01,0x09,0x12,0x02,0x0D, +0x3E,0x3F,0xB0,0xF0,0x30,0x18,0x0C,0x0C,0x0E,0x0E,0x0E,0x06,0x03,0x01,0x80,0x00,0x00,0x30,0x18,0x0C,0x00, +// '@' +0x40,0x02,0x15,0x15,0x02,0x18, +0x00,0xFC,0x00,0x3F,0xF8,0x03,0xC0,0xF0,0x38,0x01,0xC3,0x80,0x07,0x38,0x79,0x99,0x8F,0xEC,0xFC,0x71,0xE3,0xC7,0x07,0x1E,0x30,0x18,0xF1,0x80,0xC7,0x8C,0x06,0x3C,0x70,0x73,0x71,0xC7,0xB9,0x8F,0xEF,0x8E,0x1E,0x70,0x38,0x00,0x00,0xE0,0x04,0x03,0xC0,0xE0,0x0F,0xFE,0x00,0x0F,0x80,0x00, +// 'A' +0x41,0x01,0x10,0x12,0x00,0x10, +0x03,0xC0,0x03,0xC0,0x03,0xC0,0x07,0xE0,0x06,0x60,0x06,0x60,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x18,0x18,0x18,0x18,0x38,0x1C,0x3F,0xFC,0x3F,0xFC,0x60,0x06,0x60,0x06,0x60,0x06,0xC0,0x03, +// 'B' +0x42,0x01,0x0C,0x12,0x02,0x10, +0xFF,0x0F,0xFC,0xC0,0xEC,0x06,0xC0,0x6C,0x06,0xC0,0x6C,0x0C,0xFF,0x8F,0xFC,0xC0,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x06,0xFF,0xEF,0xF8, +// 'C' +0x43,0x01,0x0E,0x12,0x01,0x11, +0x07,0xE0,0x7F,0xE3,0xC1,0xDC,0x01,0x60,0x01,0x80,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0x60,0x01,0x80,0x07,0x00,0x4F,0x07,0x1F,0xF8,0x1F,0x80, +// 'D' +0x44,0x01,0x0F,0x12,0x02,0x12, +0xFF,0x81,0xFF,0xE3,0x01,0xE6,0x00,0xEC,0x00,0xD8,0x01,0xF0,0x01,0xE0,0x03,0xC0,0x07,0x80,0x0F,0x00,0x1E,0x00,0x3C,0x00,0xF8,0x01,0xB0,0x07,0x60,0x3C,0xFF,0xF1,0xFF,0x00, +// 'E' +0x45,0x01,0x0B,0x12,0x02,0x0F, +0xFF,0xFF,0xFF,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xFF,0xDF,0xFB,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xFF,0xFF,0xFC, +// 'F' +0x46,0x01,0x0A,0x12,0x02,0x0E, +0xFF,0xFF,0xFC,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00,0xFF,0xBF,0xEC,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0x00, +// 'G' +0x47,0x01,0x0F,0x12,0x01,0x13, +0x07,0xE0,0x3F,0xF0,0xE0,0x73,0x80,0x26,0x00,0x1C,0x00,0x30,0x00,0x60,0x00,0xC0,0x7F,0x80,0xFF,0x00,0x1E,0x00,0x36,0x00,0x6C,0x00,0xDC,0x01,0x9E,0x07,0x1F,0xFC,0x0F,0xE0, +// 'H' +0x48,0x01,0x0D,0x12,0x02,0x12, +0xC0,0x1E,0x00,0xF0,0x07,0x80,0x3C,0x01,0xE0,0x0F,0x00,0x78,0x03,0xFF,0xFF,0xFF,0xF0,0x07,0x80,0x3C,0x01,0xE0,0x0F,0x00,0x78,0x03,0xC0,0x1E,0x00,0xC0, +// 'I' +0x49,0x01,0x02,0x12,0x02,0x07, +0xFF,0xFF,0xFF,0xFF,0xF0, +// 'J' +0x4A,0x01,0x06,0x17,0xFE,0x07, +0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x1B,0xEF,0x00, +// 'K' +0x4B,0x01,0x0F,0x12,0x02,0x10, +0xC0,0x71,0x81,0xC3,0x07,0x06,0x1C,0x0C,0x70,0x19,0xC0,0x37,0x00,0x7C,0x00,0xF8,0x01,0xB0,0x03,0x38,0x06,0x38,0x0C,0x38,0x18,0x38,0x30,0x38,0x60,0x38,0xC0,0x39,0x80,0x38, +// 'L' +0x4C,0x01,0x0B,0x12,0x02,0x0D, +0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xFF,0xFF,0xFC, +// 'M' +0x4D,0x01,0x10,0x12,0x02,0x15, +0xE0,0x07,0xF0,0x0F,0xF0,0x0F,0xF8,0x1F,0xD8,0x1B,0xD8,0x1B,0xCC,0x33,0xCC,0x33,0xCC,0x33,0xC6,0x63,0xC6,0x63,0xC7,0xE3,0xC3,0xC3,0xC3,0xC3,0xC1,0x83,0xC0,0x03,0xC0,0x03,0xC0,0x03, +// 'N' +0x4E,0x01,0x0D,0x12,0x02,0x12, +0xE0,0x1F,0x80,0xFC,0x07,0xF0,0x3D,0x81,0xE6,0x0F,0x30,0x78,0xC3,0xC6,0x1E,0x18,0xF0,0xC7,0x83,0x3C,0x19,0xE0,0x6F,0x03,0x78,0x0F,0xC0,0x7E,0x01,0xC0, +// 'O' +0x4F,0x01,0x10,0x12,0x01,0x13, +0x07,0xE0,0x1F,0xF8,0x3C,0x3C,0x70,0x0E,0x60,0x06,0x60,0x06,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0x60,0x06,0x60,0x06,0x70,0x0E,0x3C,0x3C,0x1F,0xF8,0x07,0xE0, +// 'P' +0x50,0x01,0x0B,0x12,0x02,0x0E, +0xFF,0x1F,0xFB,0x07,0x60,0x3C,0x07,0x80,0xF0,0x1E,0x0E,0xFF,0xDF,0xE3,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x18,0x00, +// 'Q' +0x51,0x01,0x10,0x15,0x01,0x13, +0x07,0xE0,0x1F,0xF8,0x3C,0x3C,0x70,0x0E,0x60,0x06,0x60,0x06,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0x60,0x07,0x60,0x06,0x70,0x0E,0x3C,0x3C,0x1F,0xF8,0x07,0xF0,0x00,0x38,0x00,0x18,0x00,0x0C, +// 'R' +0x52,0x01,0x0D,0x12,0x02,0x11, +0xFF,0x07,0xFE,0x30,0x31,0x80,0xCC,0x06,0x60,0x33,0x01,0x98,0x18,0xFF,0xC7,0xFC,0x30,0x71,0x81,0x8C,0x06,0x60,0x33,0x01,0xD8,0x06,0xC0,0x36,0x00,0xC0, +// 'S' +0x53,0x01,0x0C,0x12,0x02,0x0F, +0x1F,0x87,0xFE,0x70,0x6C,0x00,0xC0,0x0C,0x00,0xC0,0x07,0x00,0x7F,0x01,0xFC,0x00,0xE0,0x07,0x00,0x30,0x03,0x00,0x3C,0x0E,0xFF,0xE3,0xF8, +// 'T' +0x54,0x01,0x0E,0x12,0x00,0x0F, +0xFF,0xFF,0xFF,0xF0,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00, +// 'U' +0x55,0x01,0x0D,0x12,0x02,0x12, +0xC0,0x1E,0x00,0xF0,0x07,0x80,0x3C,0x01,0xE0,0x0F,0x00,0x78,0x03,0xC0,0x1E,0x00,0xF0,0x07,0x80,0x3C,0x01,0xE0,0x0D,0x80,0xCE,0x0E,0x3F,0xE0,0x7C,0x00, +// 'V' +0x56,0x01,0x10,0x12,0x00,0x10, +0xC0,0x03,0x60,0x06,0x60,0x06,0x60,0x06,0x30,0x0C,0x30,0x0C,0x38,0x1C,0x18,0x18,0x18,0x18,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x06,0x60,0x06,0x60,0x07,0x60,0x03,0xC0,0x03,0xC0,0x03,0xC0, +// 'W' +0x57,0x01,0x16,0x12,0x01,0x18, +0xC0,0x78,0x0F,0x01,0xE0,0x36,0x07,0x81,0x98,0x1E,0x06,0x60,0xEC,0x19,0x83,0x30,0x63,0x0C,0xC3,0x0C,0x33,0x0C,0x30,0xCE,0x30,0xC6,0x18,0xC1,0x98,0x66,0x06,0x61,0x98,0x19,0x86,0x60,0x6C,0x0D,0x80,0xF0,0x3C,0x03,0xC0,0xF0,0x0F,0x03,0xC0,0x38,0x07,0x00, +// 'X' +0x58,0x01,0x0F,0x12,0x01,0x11, +0x70,0x0E,0x60,0x18,0x60,0x60,0xE1,0xC0,0xC7,0x00,0xCC,0x01,0xF0,0x01,0xE0,0x03,0x80,0x07,0x80,0x1F,0x00,0x37,0x00,0xC6,0x03,0x86,0x0E,0x0E,0x18,0x0C,0x60,0x0D,0xC0,0x1C, +// 'Y' +0x59,0x01,0x0E,0x12,0x00,0x0F, +0xE0,0x1D,0x80,0x63,0x03,0x0E,0x1C,0x18,0x60,0x33,0x00,0xFC,0x01,0xE0,0x07,0x80,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00, +// 'Z' +0x5A,0x01,0x0E,0x12,0x01,0x10, +0xFF,0xFF,0xFF,0xF0,0x01,0x80,0x0E,0x00,0x70,0x01,0x80,0x0C,0x00,0x60,0x03,0x80,0x1C,0x00,0x60,0x03,0x00,0x18,0x00,0xE0,0x07,0x00,0x18,0x00,0xFF,0xFF,0xFF,0xF0, +// '[' +0x5B,0x01,0x05,0x15,0x02,0x09, +0xFF,0xF1,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0xFF,0x80, +// '\' +0x5C,0x01,0x08,0x14,0x00,0x08, +0xC0,0xE0,0x60,0x60,0x60,0x30,0x30,0x30,0x18,0x18,0x18,0x18,0x0C,0x0C,0x0C,0x06,0x06,0x06,0x07,0x03, +// ']' +0x5D,0x01,0x05,0x15,0x02,0x09, +0xFF,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC7,0xFF,0x80, +// '^' +0x5E,0x01,0x0F,0x07,0x03,0x14, +0x03,0x80,0x0F,0x80,0x3B,0x80,0xE3,0x83,0x83,0x8E,0x03,0xB8,0x03,0x80, +// '_' +0x5F,0x17,0x0C,0x02,0x00,0x0C, +0xFF,0xFF,0xFF, +// '`' +0x60,0x00,0x06,0x04,0x02,0x0C, +0x60,0xC1,0x83, +// 'a' +0x61,0x06,0x0B,0x0D,0x01,0x0E, +0x3F,0x0F,0xF9,0x03,0x00,0x30,0x06,0x3F,0xDF,0xFF,0x03,0xC0,0x78,0x1F,0x87,0xBF,0xF3,0xE6, +// 'b' +0x62,0x01,0x0C,0x12,0x02,0x0F, +0xC0,0x0C,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0xF8,0xFF,0xCF,0x0E,0xE0,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xE0,0x6F,0x0E,0xFF,0xCC,0xF8, +// 'c' +0x63,0x06,0x0A,0x0D,0x01,0x0D, +0x0F,0x8F,0xF7,0x05,0x80,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x18,0x07,0x04,0xFF,0x0F,0x80, +// 'd' +0x64,0x01,0x0C,0x12,0x01,0x0F, +0x00,0x30,0x03,0x00,0x30,0x03,0x00,0x31,0xF3,0x3F,0xF7,0x0F,0x60,0x7C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x77,0x0F,0x3F,0xF1,0xF3, +// 'e' +0x65,0x06,0x0C,0x0D,0x01,0x0E, +0x0F,0x83,0xFC,0x70,0xE6,0x07,0xC0,0x3F,0xFF,0xFF,0xFC,0x00,0xC0,0x06,0x00,0x70,0x23,0xFE,0x0F,0xC0, +// 'f' +0x66,0x01,0x08,0x12,0x01,0x08, +0x0F,0x1F,0x38,0x30,0x30,0xFF,0xFF,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30, +// 'g' +0x67,0x06,0x0C,0x12,0x01,0x0F, +0x1F,0x33,0xFF,0x70,0xF6,0x07,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x07,0x70,0xF3,0xFF,0x1F,0x30,0x03,0x00,0x72,0x0E,0x3F,0xC1,0xF8, +// 'h' +0x68,0x01,0x0B,0x12,0x02,0x0F, +0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x9F,0x3F,0xF7,0x87,0xE0,0x78,0x0F,0x01,0xE0,0x3C,0x07,0x80,0xF0,0x1E,0x03,0xC0,0x78,0x0C, +// 'i' +0x69,0x01,0x02,0x12,0x02,0x07, +0xFC,0x3F,0xFF,0xFF,0xF0, +// 'j' +0x6A,0x01,0x05,0x17,0xFF,0x07, +0x18,0xC6,0x00,0x0C,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x33,0xFB,0x80, +// 'k' +0x6B,0x01,0x0C,0x12,0x02,0x0E, +0xC0,0x0C,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0x1C,0xC3,0x8C,0x70,0xCE,0x0D,0xC0,0xF8,0x0F,0x80,0xDC,0x0C,0xE0,0xC7,0x0C,0x38,0xC1,0xCC,0x0E, +// 'l' +0x6C,0x01,0x02,0x12,0x02,0x06, +0xFF,0xFF,0xFF,0xFF,0xF0, +// 'm' +0x6D,0x06,0x14,0x0D,0x02,0x18, +0xCF,0x87,0xCF,0xFC,0xFE,0xF0,0xF8,0x7E,0x07,0x03,0xC0,0x60,0x3C,0x06,0x03,0xC0,0x60,0x3C,0x06,0x03,0xC0,0x60,0x3C,0x06,0x03,0xC0,0x60,0x3C,0x06,0x03,0xC0,0x60,0x30, +// 'n' +0x6E,0x06,0x0B,0x0D,0x02,0x0F, +0xCF,0x9F,0xFB,0xC3,0xF0,0x3C,0x07,0x80,0xF0,0x1E,0x03,0xC0,0x78,0x0F,0x01,0xE0,0x3C,0x06, +// 'o' +0x6F,0x06,0x0C,0x0D,0x01,0x0E, +0x1F,0x83,0xFC,0x70,0xE6,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x70,0xE3,0xFC,0x1F,0x80, +// 'p' +0x70,0x06,0x0C,0x12,0x02,0x0F, +0xCF,0x8F,0xFC,0xF0,0xEE,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x3E,0x06,0xF0,0xEF,0xFC,0xCF,0x8C,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0x00, +// 'q' +0x71,0x06,0x0C,0x12,0x01,0x0F, +0x1F,0x33,0xFF,0x70,0xF6,0x07,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x07,0x70,0xF3,0xFF,0x1F,0x30,0x03,0x00,0x30,0x03,0x00,0x30,0x03, +// 'r' +0x72,0x06,0x08,0x0D,0x02,0x0A, +0xCF,0xFF,0xF0,0xE0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, +// 's' +0x73,0x06,0x0B,0x0D,0x01,0x0C, +0x3F,0x0F,0xF3,0x82,0x60,0x0C,0x00,0xF0,0x0F,0xC0,0x3C,0x00,0xC0,0x1A,0x07,0x7F,0xC7,0xF0, +// 't' +0x74,0x02,0x08,0x11,0x00,0x09, +0x30,0x30,0x30,0x30,0xFF,0xFF,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x1F,0x0F, +// 'u' +0x75,0x06,0x0B,0x0D,0x02,0x0F, +0xC0,0x78,0x0F,0x01,0xE0,0x3C,0x07,0x80,0xF0,0x1E,0x03,0xC0,0x78,0x1F,0x87,0xBF,0xF3,0xE6, +// 'v' +0x76,0x06,0x0D,0x0D,0x01,0x0F, +0xC0,0x1B,0x01,0x98,0x0C,0xC0,0x63,0x06,0x18,0x30,0x63,0x03,0x18,0x18,0xC0,0x6C,0x03,0x60,0x1F,0x00,0x70,0x00, +// 'w' +0x77,0x06,0x12,0x0D,0x01,0x14, +0xC1,0xE0,0xF0,0x78,0x36,0x1E,0x19,0x87,0x86,0x63,0x31,0x9C,0xCC,0xE3,0x33,0x30,0xCC,0xCC,0x36,0x1B,0x07,0x87,0x81,0xE1,0xE0,0x78,0x78,0x1C,0x0E,0x00, +// 'x' +0x78,0x06,0x0D,0x0D,0x01,0x0F, +0xE0,0x3B,0x83,0x8E,0x38,0x31,0x80,0xD8,0x07,0xC0,0x1C,0x01,0xF0,0x1D,0xC0,0xC6,0x0C,0x18,0xE0,0xEE,0x03,0x80, +// 'y' +0x79,0x06,0x0D,0x12,0x01,0x0F, +0xC0,0x1B,0x01,0x98,0x0C,0xE0,0xE3,0x06,0x18,0x70,0x63,0x03,0x18,0x0D,0x80,0x6C,0x03,0xE0,0x0E,0x00,0x70,0x03,0x00,0x18,0x01,0x80,0x7C,0x03,0xC0,0x00, +// 'z' +0x7A,0x06,0x0B,0x0D,0x01,0x0D, +0xFF,0xFF,0xFC,0x03,0x00,0xE0,0x38,0x0E,0x03,0x80,0xE0,0x38,0x0E,0x01,0x80,0x7F,0xFF,0xFE, +// '{' +0x7B,0x01,0x09,0x16,0x03,0x0F, +0x03,0x83,0xC3,0x81,0x80,0xC0,0x60,0x30,0x18,0x0C,0x0E,0x3E,0x1F,0x01,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0xC0,0x78,0x1C, +// '|' +0x7C,0x01,0x02,0x18,0x03,0x08, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +// '}' +0x7D,0x01,0x09,0x16,0x03,0x0F, +0xE0,0x78,0x0E,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0E,0x03,0xE1,0xF1,0xC0,0xC0,0x60,0x30,0x18,0x0C,0x06,0x07,0x0F,0x07,0x00, +// '~' +0x7E,0x09,0x0F,0x05,0x03,0x14, +0x00,0x00,0x7C,0x05,0xFE,0x1E,0x1F,0xE0,0x0F,0x80, + +// Terminator +0xFF +}; diff --git a/components/epaper/EPD.c b/components/epaper/EPD.c new file mode 100644 index 0000000..e8b74e7 --- /dev/null +++ b/components/epaper/EPD.c @@ -0,0 +1,2322 @@ +/* EPD library + * + * Author: LoBo (loboris@gmail.com, loboris.github) + * + * Module supporting SPI ePaper displays +*/ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "time.h" +#include +#include "rom/tjpgd.h" +#include "esp_heap_alloc_caps.h" +#include "EPD.h" +#include "EPDspi.h" +#include "rom/tjpgd.h" + + +#define DEG_TO_RAD 0.01745329252 +#define RAD_TO_DEG 57.295779513 +#define deg_to_rad 0.01745329252 + 3.14159265359 +#define swap(a, b) { int16_t t = a; a = b; b = t; } +#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) + +#if !defined(max) +#define max(A,B) ( (A) > (B) ? (A):(B)) +#endif + +#if !defined(min) +#define min(A,B) ( (A) < (B) ? (A):(B)) +#endif + +// Embedded fonts +extern uint8_t tft_SmallFont[]; +extern uint8_t tft_DefaultFont[]; +extern uint8_t tft_Dejavu18[]; +extern uint8_t tft_Dejavu24[]; +extern uint8_t tft_Ubuntu16[]; +extern uint8_t tft_Comic24[]; +extern uint8_t tft_minya24[]; +extern uint8_t tft_tooney32[]; + +// ============================================================== +// ==== Set default values of global variables ================== + +uint8_t orientation = LANDSCAPE_0; // screen orientation +uint16_t font_rotate = 0; // font rotation +uint8_t font_transparent = 0; +uint8_t font_forceFixed = 0; +uint8_t text_wrap = 0; // character wrapping to new line +color_t _fg = EPD_BLACK; +color_t _bg = EPD_WHITE; + +float _angleOffset = DEFAULT_ANGLE_OFFSET; + +int EPD_X = 0; +int EPD_Y = 0; + +dispWin_t dispWin = { + .x1 = 0, + .y1 = 0, + .x2 = EPD_DISPLAY_WIDTH-1, + .y2 = EPD_DISPLAY_HEIGHT-1, +}; + +Font_t cfont = { + .font = tft_DefaultFont, + .x_size = 0, + .y_size = 0x0B, + .offset = 0, + .numchars = 95, + .bitmap = 1, +}; + +uint8_t font_line_space = 0; +uint8_t image_debug = 0; + +// ============================================================== + + +typedef struct { + uint8_t charCode; + int adjYOffset; + int width; + int height; + int xOffset; + int xDelta; + uint16_t dataPtr; +} propFont; + +static dispWin_t dispWinTemp; + +static uint8_t *userfont = NULL; +static int EPD_OFFSET = 0; +static propFont fontChar; +static float _arcAngleMax = DEFAULT_ARC_ANGLE_MAX; + + + +// ============================================================================================================== +//---------------------------------------------- +static void drawPixel(int x, int y, uint8_t val) +{ + if (orientation == LANDSCAPE_180) { + x = _width - x - 1; + y = _height - y - 1; + } + if (_gs) { + val &= 0x0F; + //if (gs_drawBuff[(y * _width) + x] != val) { + gs_drawBuff[(y * _width) + x] = val; + gs_used_shades |= (1<> 3)) + (y>>3)]; + uint8_t new_val = buf_val; + if (val) new_val &= (0x80 >> (y % 8)) ^ 0xFF; + else new_val |= (0x80 >> (y % 8)); + //if (new_val != buf_val) drawBuff[(x * (_height>>3)) + (y>>3)] = new_val; + drawBuff[(x * (_height>>3)) + (y>>3)] = new_val; + } +} + +//------------------------------------------------------------------------- +static void EPD_pushColorRep(int x1, int y1, int x2, int y2, color_t color) +{ + if (_gs == 0) color &= 0x01; + else color &= 0x0F; + for (int y=y1; y<=y2; y++) { + for (int x = x1; x<=x2; x++){ + drawPixel(x, y, color); + } + } +} + +/* +//--------------------------------------------------------------------------------------- +static void copyBuff(int x1, int y1, int x2, int y2, uint8_t *src_buf, uint8_t *dest_buf) +{ + if + uint8_t buf_val1 = (src_buf[(x1 * (EPD_DISPLAY_HEIGHT>>3)) + (y1>>3)]) ; + uint8_t val = 0x80 >> (y1 % 8); + if (val) buf_val &= (0x80 >> (y % 8)) ^ 0xFF; + else buf_val |= (0x80 >> (y % 8)); + + drawBuff[(x * (EPD_DISPLAY_HEIGHT>>3)) + (y>>3)] = buf_val; +} +*/ + +// ============================================================================================================== + +// ========================================================================= +// ** All drawings are clipped to 'dispWin' ** +// ** All x,y coordinates in public functions are relative to clip window ** +// =========== : Public functions +// ----------- : Local functions +// ========================================================================= + + +// Compare two colors; return 0 if equal +//============================================ +int EPD_compare_colors(color_t c1, color_t c2) +{ + if (c1 != c2) return 1; + + return 0; +} + +// draw color pixel on screen +//----------------------------------------------------------- +static void _drawPixel(int16_t x, int16_t y, color_t color) { + + if ((x < dispWin.x1) || (y < dispWin.y1) || (x > dispWin.x2) || (y > dispWin.y2)) return; + drawPixel(x, y, color); +} + +//======================================================= +void EPD_drawPixel(int16_t x, int16_t y, color_t color) { + + _drawPixel(x+dispWin.x1, y+dispWin.y1, color); +} + +//-------------------------------------------------------------------------- +static void _drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) { + // clipping + if ((x < dispWin.x1) || (x > dispWin.x2) || (y > dispWin.y2)) return; + if (y < dispWin.y1) { + h -= (dispWin.y1 - y); + y = dispWin.y1; + } + if (h < 0) h = 0; + if ((y + h) > (dispWin.y2+1)) h = dispWin.y2 - y + 1; + if (h == 0) h = 1; + EPD_pushColorRep(x, y, x, y+h-1, color); +} + +//-------------------------------------------------------------------------- +static void _drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) { + // clipping + if ((y < dispWin.y1) || (x > dispWin.x2) || (y > dispWin.y2)) return; + if (x < dispWin.x1) { + w -= (dispWin.x1 - x); + x = dispWin.x1; + } + if (w < 0) w = 0; + if ((x + w) > (dispWin.x2+1)) w = dispWin.x2 - x + 1; + if (w == 0) w = 1; + + EPD_pushColorRep(x, y, x+w-1, y, color); +} + +//====================================================================== +void EPD_drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) { + _drawFastVLine(x+dispWin.x1, y+dispWin.y1, h, color); +} + +//====================================================================== +void EPD_drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) { + _drawFastHLine(x+dispWin.x1, y+dispWin.y1, w, color); +} + +// Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer this uses +// the eficient FastH/V Line draw routine for segments of 2 pixels or more +//---------------------------------------------------------------------------------- +static void _drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color) +{ + if (x0 == x1) { + if (y0 <= y1) _drawFastVLine(x0, y0, y1-y0, color); + else _drawFastVLine(x0, y1, y0-y1, color); + return; + } + if (y0 == y1) { + if (x0 <= x1) _drawFastHLine(x0, y0, x1-x0, color); + else _drawFastHLine(x1, y0, x0-x1, color); + return; + } + + int steep = 0; + if (abs(y1 - y0) > abs(x1 - x0)) steep = 1; + if (steep) { + swap(x0, y0); + swap(x1, y1); + } + if (x0 > x1) { + swap(x0, x1); + swap(y0, y1); + } + + int16_t dx = x1 - x0, dy = abs(y1 - y0);; + int16_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0; + + if (y0 < y1) ystep = 1; + + // Split into steep and not steep for FastH/V separation + if (steep) { + for (; x0 <= x1; x0++) { + dlen++; + err -= dy; + if (err < 0) { + err += dx; + if (dlen == 1) _drawPixel(y0, xs, color); + else _drawFastVLine(y0, xs, dlen, color); + dlen = 0; y0 += ystep; xs = x0 + 1; + } + } + if (dlen) _drawFastVLine(y0, xs, dlen, color); + } + else + { + for (; x0 <= x1; x0++) { + dlen++; + err -= dy; + if (err < 0) { + err += dx; + if (dlen == 1) _drawPixel(xs, y0, color); + else _drawFastHLine(xs, y0, dlen, color); + dlen = 0; y0 += ystep; xs = x0 + 1; + } + } + if (dlen) _drawFastHLine(xs, y0, dlen, color); + } +} + +//============================================================================== +void EPD_drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color) +{ + _drawLine(x0+dispWin.x1, y0+dispWin.y1, x1+dispWin.x1, y1+dispWin.y1, color); +} + +// fill a rectangle +//-------------------------------------------------------------------------------- +static void _fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) { + // clipping + if ((x >= dispWin.x2) || (y > dispWin.y2)) return; + + if (x < dispWin.x1) { + w -= (dispWin.x1 - x); + x = dispWin.x1; + } + if (y < dispWin.y1) { + h -= (dispWin.y1 - y); + y = dispWin.y1; + } + if (w < 0) w = 0; + if (h < 0) h = 0; + + if ((x + w) > (dispWin.x2+1)) w = dispWin.x2 - x + 1; + if ((y + h) > (dispWin.y2+1)) h = dispWin.y2 - y + 1; + if (w == 0) w = 1; + if (h == 0) h = 1; + EPD_pushColorRep(x, y, x+w-1, y+h-1, color); +} + +//============================================================================ +void EPD_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) { + _fillRect(x+dispWin.x1, y+dispWin.y1, w, h, color); +} + +//================================== +void EPD_fillScreen(color_t color) { + color &= 0x0F; + memset(disp_buffer, ((color&1) ? 0 : 0xFF), _width * (_height/8)); + memset(gs_disp_buffer, color, _width * _height); + gs_used_shades = 0; +} + +//================================== +void EPD_fillWindow(color_t color) { + EPD_pushColorRep(dispWin.x1, dispWin.y1, dispWin.x2, dispWin.y2, 0xFF); +} + +// ^^^============= Basics drawing functions ================================^^^ + + +// ================ Graphics drawing functions ================================== + +//----------------------------------------------------------------------------------- +static void _drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) { + _drawFastHLine(x1,y1,w, color); + _drawFastVLine(x1+w-1,y1,h, color); + _drawFastHLine(x1,y1+h-1,w, color); + _drawFastVLine(x1,y1,h, color); +} + +//=============================================================================== +void EPD_drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) { + _drawRect(x1+dispWin.x1, y1+dispWin.y1, w, h, color); +} + +//------------------------------------------------------------------------------------------------- +static void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, color_t color) +{ + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (cornername & 0x4) { + _drawPixel(x0 + x, y0 + y, color); + _drawPixel(x0 + y, y0 + x, color); + } + if (cornername & 0x2) { + _drawPixel(x0 + x, y0 - y, color); + _drawPixel(x0 + y, y0 - x, color); + } + if (cornername & 0x8) { + _drawPixel(x0 - y, y0 + x, color); + _drawPixel(x0 - x, y0 + y, color); + } + if (cornername & 0x1) { + _drawPixel(x0 - y, y0 - x, color); + _drawPixel(x0 - x, y0 - y, color); + } + } +} + +// Used to do circles and roundrects +//---------------------------------------------------------------------------------------------------------------- +static void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, color_t color) +{ + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + int16_t ylm = x0 - r; + + while (x < y) { + if (f >= 0) { + if (cornername & 0x1) _drawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, color); + if (cornername & 0x2) _drawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, color); + ylm = x0 - y; + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + if ((x0 - x) > ylm) { + if (cornername & 0x1) _drawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, color); + if (cornername & 0x2) _drawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, color); + } + } +} + +// Draw a rounded rectangle +//============================================================================================= +void EPD_drawRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color) +{ + x += dispWin.x1; + y += dispWin.y1; + + // smarter version + _drawFastHLine(x + r, y, w - 2 * r, color); // Top + _drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom + _drawFastVLine(x, y + r, h - 2 * r, color); // Left + _drawFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right + + // draw four corners + drawCircleHelper(x + r, y + r, r, 1, color); + drawCircleHelper(x + w - r - 1, y + r, r, 2, color); + drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color); + drawCircleHelper(x + r, y + h - r - 1, r, 8, color); +} + +// Fill a rounded rectangle +//============================================================================================= +void EPD_fillRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color) +{ + x += dispWin.x1; + y += dispWin.y1; + + // smarter version + _fillRect(x + r, y, w - 2 * r, h, color); + + // draw four corners + fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); + fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color); +} + + + + +//----------------------------------------------------------------------------------------------- +static void _drawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t length, color_t color) +{ + _drawLine( + x, + y, + x + length * cos((angle + _angleOffset) * DEG_TO_RAD), + y + length * sin((angle + _angleOffset) * DEG_TO_RAD), color); +} + +//--------------------------------------------------------------------------------------------------------------- +static void _DrawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t start, uint16_t length, color_t color) +{ + _drawLine( + x + start * cos((angle + _angleOffset) * DEG_TO_RAD), + y + start * sin((angle + _angleOffset) * DEG_TO_RAD), + x + (start + length) * cos((angle + _angleOffset) * DEG_TO_RAD), + y + (start + length) * sin((angle + _angleOffset) * DEG_TO_RAD), color); +} + +//=========================================================================================================== +void EPD_drawLineByAngle(uint16_t x, uint16_t y, uint16_t start, uint16_t len, uint16_t angle, color_t color) +{ + x += dispWin.x1; + y += dispWin.y1; + + if (start == 0) _drawLineByAngle(x, y, angle, len, color); + else _DrawLineByAngle(x, y, angle, start, len, color); +} + + +// Draw a triangle +//-------------------------------------------------------------------------------------------------------------------- +static void _drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) +{ + _drawLine(x0, y0, x1, y1, color); + _drawLine(x1, y1, x2, y2, color); + _drawLine(x2, y2, x0, y0, color); +} + +//================================================================================================================ +void EPD_drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) +{ + x0 += dispWin.x1; + y0 += dispWin.y1; + x1 += dispWin.x1; + y1 += dispWin.y1; + x2 += dispWin.x1; + y2 += dispWin.y1; + + _drawLine(x0, y0, x1, y1, color); + _drawLine(x1, y1, x2, y2, color); + _drawLine(x2, y2, x0, y0, color); +} + +// Fill a triangle +//-------------------------------------------------------------------------------------------------------------------- +static void _fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) +{ + int16_t a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + swap(y0, y1); swap(x0, x1); + } + if (y1 > y2) { + swap(y2, y1); swap(x2, x1); + } + if (y0 > y1) { + swap(y0, y1); swap(x0, x1); + } + + if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if(x1 < a) a = x1; + else if(x1 > b) b = x1; + if(x2 < a) a = x2; + else if(x2 > b) b = x2; + _drawFastHLine(a, y0, b-a+1, color); + return; + } + + int16_t + dx01 = x1 - x0, + dy01 = y1 - y0, + dx02 = x2 - x0, + dy02 = y2 - y0, + dx12 = x2 - x1, + dy12 = y2 - y1; + int32_t + sa = 0, + sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if(y1 == y2) last = y1; // Include y1 scanline + else last = y1-1; // Skip it + + for(y=y0; y<=last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if(a > b) swap(a,b); + _drawFastHLine(a, y, b-a+1, color); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for(; y<=y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if(a > b) swap(a,b); + _drawFastHLine(a, y, b-a+1, color); + } +} + +//================================================================================================================ +void EPD_fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) +{ + _fillTriangle( + x0 + dispWin.x1, y0 + dispWin.y1, + x1 + dispWin.x1, y1 + dispWin.y1, + x2 + dispWin.x1, y2 + dispWin.y1, + color); +} + +//==================================================================== +void EPD_drawCircle(int16_t x, int16_t y, int radius, color_t color) { + x += dispWin.x1; + y += dispWin.y1; + int f = 1 - radius; + int ddF_x = 1; + int ddF_y = -2 * radius; + int x1 = 0; + int y1 = radius; + + _drawPixel(x, y + radius, color); + _drawPixel(x, y - radius, color); + _drawPixel(x + radius, y, color); + _drawPixel(x - radius, y, color); + while(x1 < y1) { + if (f >= 0) { + y1--; + ddF_y += 2; + f += ddF_y; + } + x1++; + ddF_x += 2; + f += ddF_x; + _drawPixel(x + x1, y + y1, color); + _drawPixel(x - x1, y + y1, color); + _drawPixel(x + x1, y - y1, color); + _drawPixel(x - x1, y - y1, color); + _drawPixel(x + y1, y + x1, color); + _drawPixel(x - y1, y + x1, color); + _drawPixel(x + y1, y - x1, color); + _drawPixel(x - y1, y - x1, color); + } +} + +//==================================================================== +void EPD_fillCircle(int16_t x, int16_t y, int radius, color_t color) { + x += dispWin.x1; + y += dispWin.y1; + + _drawFastVLine(x, y-radius, 2*radius+1, color); + fillCircleHelper(x, y, radius, 3, 0, color); +} + +//---------------------------------------------------------------------------------------------------------------- +static void _draw_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option) +{ + // upper right + if ( option & EPD_ELLIPSE_UPPER_RIGHT ) _drawPixel(x0 + x, y0 - y, color); + // upper left + if ( option & EPD_ELLIPSE_UPPER_LEFT ) _drawPixel(x0 - x, y0 - y, color); + // lower right + if ( option & EPD_ELLIPSE_LOWER_RIGHT ) _drawPixel(x0 + x, y0 + y, color); + // lower left + if ( option & EPD_ELLIPSE_LOWER_LEFT ) _drawPixel(x0 - x, y0 + y, color); +} + +//===================================================================================================== +void EPD_drawEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option) +{ + x0 += dispWin.x1; + y0 += dispWin.y1; + + uint16_t x, y; + int32_t xchg, ychg; + int32_t err; + int32_t rxrx2; + int32_t ryry2; + int32_t stopx, stopy; + + rxrx2 = rx; + rxrx2 *= rx; + rxrx2 *= 2; + + ryry2 = ry; + ryry2 *= ry; + ryry2 *= 2; + + x = rx; + y = 0; + + xchg = 1; + xchg -= rx; + xchg -= rx; + xchg *= ry; + xchg *= ry; + + ychg = rx; + ychg *= rx; + + err = 0; + + stopx = ryry2; + stopx *= rx; + stopy = 0; + + while( stopx >= stopy ) { + _draw_ellipse_section(x, y, x0, y0, color, option); + y++; + stopy += rxrx2; + err += ychg; + ychg += rxrx2; + if ( 2*err+xchg > 0 ) { + x--; + stopx -= ryry2; + err += xchg; + xchg += ryry2; + } + } + + x = 0; + y = ry; + + xchg = ry; + xchg *= ry; + + ychg = 1; + ychg -= ry; + ychg -= ry; + ychg *= rx; + ychg *= rx; + + err = 0; + + stopx = 0; + + stopy = rxrx2; + stopy *= ry; + + while( stopx <= stopy ) { + _draw_ellipse_section(x, y, x0, y0, color, option); + x++; + stopx += ryry2; + err += xchg; + xchg += ryry2; + if ( 2*err+ychg > 0 ) { + y--; + stopy -= rxrx2; + err += ychg; + ychg += rxrx2; + } + } +} + +//----------------------------------------------------------------------------------------------------------------------- +static void _draw_filled_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option) +{ + // upper right + if ( option & EPD_ELLIPSE_UPPER_RIGHT ) _drawFastVLine(x0+x, y0-y, y+1, color); + // upper left + if ( option & EPD_ELLIPSE_UPPER_LEFT ) _drawFastVLine(x0-x, y0-y, y+1, color); + // lower right + if ( option & EPD_ELLIPSE_LOWER_RIGHT ) _drawFastVLine(x0+x, y0, y+1, color); + // lower left + if ( option & EPD_ELLIPSE_LOWER_LEFT ) _drawFastVLine(x0-x, y0, y+1, color); +} + +//===================================================================================================== +void EPD_fillEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option) +{ + x0 += dispWin.x1; + y0 += dispWin.y1; + + uint16_t x, y; + int32_t xchg, ychg; + int32_t err; + int32_t rxrx2; + int32_t ryry2; + int32_t stopx, stopy; + + rxrx2 = rx; + rxrx2 *= rx; + rxrx2 *= 2; + + ryry2 = ry; + ryry2 *= ry; + ryry2 *= 2; + + x = rx; + y = 0; + + xchg = 1; + xchg -= rx; + xchg -= rx; + xchg *= ry; + xchg *= ry; + + ychg = rx; + ychg *= rx; + + err = 0; + + stopx = ryry2; + stopx *= rx; + stopy = 0; + + while( stopx >= stopy ) { + _draw_filled_ellipse_section(x, y, x0, y0, color, option); + y++; + stopy += rxrx2; + err += ychg; + ychg += rxrx2; + if ( 2*err+xchg > 0 ) { + x--; + stopx -= ryry2; + err += xchg; + xchg += ryry2; + } + } + + x = 0; + y = ry; + + xchg = ry; + xchg *= ry; + + ychg = 1; + ychg -= ry; + ychg -= ry; + ychg *= rx; + ychg *= rx; + + err = 0; + + stopx = 0; + + stopy = rxrx2; + stopy *= ry; + + while( stopx <= stopy ) { + _draw_filled_ellipse_section(x, y, x0, y0, color, option); + x++; + stopx += ryry2; + err += xchg; + xchg += ryry2; + if ( 2*err+ychg > 0 ) { + y--; + stopy -= rxrx2; + err += ychg; + ychg += rxrx2; + } + } +} + + +// ==== ARC DRAWING =================================================================== + +//--------------------------------------------------------------------------------------------------------------------------------- +static void _fillArcOffsetted(uint16_t cx, uint16_t cy, uint16_t radius, uint16_t thickness, float start, float end, color_t color) +{ + //float sslope = (float)cos_lookup(start) / (float)sin_lookup(start); + //float eslope = (float)cos_lookup(end) / (float)sin_lookup(end); + float sslope = (cos(start/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(start/_arcAngleMax * 2 * PI) * _arcAngleMax) ; + float eslope = (cos(end/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(end/_arcAngleMax * 2 * PI) * _arcAngleMax); + + if (end == 360) eslope = -1000000; + + int ir2 = (radius - thickness) * (radius - thickness); + int or2 = radius * radius; + + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + int x2 = x * x; + int y2 = y * y; + + if ( + (x2 + y2 < or2 && x2 + y2 >= ir2) && + ( + (y > 0 && start < 180 && x <= y * sslope) || + (y < 0 && start > 180 && x >= y * sslope) || + (y < 0 && start <= 180) || + (y == 0 && start <= 180 && x < 0) || + (y == 0 && start == 0 && x > 0) + ) && + ( + (y > 0 && end < 180 && x >= y * eslope) || + (y < 0 && end > 180 && x <= y * eslope) || + (y > 0 && end >= 180) || + (y == 0 && end >= 180 && x < 0) || + (y == 0 && start == 0 && x > 0) + ) + ) + _drawPixel(cx+x, cy+y, color); + } + } +} + + +//=========================================================================================================================== +void EPD_drawArc(uint16_t cx, uint16_t cy, uint16_t r, uint16_t th, float start, float end, color_t color, color_t fillcolor) +{ + cx += dispWin.x1; + cy += dispWin.y1; + + if (th < 1) th = 1; + if (th > r) th = r; + + int f = EPD_compare_colors(fillcolor, color); + + float astart = fmodf(start, _arcAngleMax); + float aend = fmodf(end, _arcAngleMax); + + astart += _angleOffset; + aend += _angleOffset; + + if (astart < 0) astart += (float)360; + if (aend < 0) aend += (float)360; + + if (aend == 0) aend = (float)360; + + if (astart > aend) { + _fillArcOffsetted(cx, cy, r, th, astart, _arcAngleMax, fillcolor); + _fillArcOffsetted(cx, cy, r, th, 0, aend, fillcolor); + if (f) { + _fillArcOffsetted(cx, cy, r, 1, astart, _arcAngleMax, color); + _fillArcOffsetted(cx, cy, r, 1, 0, aend, color); + _fillArcOffsetted(cx, cy, r-th, 1, astart, _arcAngleMax, color); + _fillArcOffsetted(cx, cy, r-th, 1, 0, aend, color); + } + } + else { + _fillArcOffsetted(cx, cy, r, th, astart, aend, fillcolor); + if (f) { + _fillArcOffsetted(cx, cy, r, 1, astart, aend, color); + _fillArcOffsetted(cx, cy, r-th, 1, astart, aend, color); + } + } + if (f) { + _drawLine(cx + (r-th) * cos(astart * DEG_TO_RAD), cy + (r-th) * sin(astart * DEG_TO_RAD), + cx + (r-1) * cos(astart * DEG_TO_RAD), cy + (r-1) * sin(astart * DEG_TO_RAD), color); + _drawLine(cx + (r-th) * cos(aend * DEG_TO_RAD), cy + (r-th) * sin(aend * DEG_TO_RAD), + cx + (r-1) * cos(aend * DEG_TO_RAD), cy + (r-1) * sin(aend * DEG_TO_RAD), color); + } +} + +//============================================================================================================= +void EPD_drawPolygon(int cx, int cy, int sides, int diameter, color_t color, color_t fill, int rot, uint8_t th) +{ + cx += dispWin.x1; + cy += dispWin.y1; + + int deg = rot - _angleOffset; + int f = EPD_compare_colors(fill, color); + + if (sides < MIN_POLIGON_SIDES) sides = MIN_POLIGON_SIDES; // This ensures the minimum side number + if (sides > MAX_POLIGON_SIDES) sides = MAX_POLIGON_SIDES; // This ensures the maximum side number + + int Xpoints[sides], Ypoints[sides]; // Set the arrays based on the number of sides entered + int rads = 360 / sides; // This equally spaces the points. + + for (int idx = 0; idx < sides; idx++) { + Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * diameter; + Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * diameter; + } + + // Draw the polygon on the screen. + if (f) { + for(int idx = 0; idx < sides; idx++) { + if((idx+1) < sides) _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], fill); + else _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], fill); + } + } + + if (th) { + for (int n=0; n 0) { + for (int idx = 0; idx < sides; idx++) { + Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * (diameter-n); + Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * (diameter-n); + } + } + for(int idx = 0; idx < sides; idx++) { + if( (idx+1) < sides) + _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], color); // draw the lines + else + _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], color); // finishes the last line to close up the polygon. + } + } + } +} + +/* +// Similar to the Polygon function. +//===================================================================================== +void EPD_drawStar(int cx, int cy, int diameter, color_t color, bool fill, float factor) +{ + cx += dispWin.x1; + cy += dispWin.y1; + + factor = constrain(factor, 1.0, 4.0); + uint8_t sides = 5; + uint8_t rads = 360 / sides; + + int Xpoints_O[sides], Ypoints_O[sides], Xpoints_I[sides], Ypoints_I[sides];//Xpoints_T[5], Ypoints_T[5]; + + for(int idx = 0; idx < sides; idx++) { + // makes the outer points + Xpoints_O[idx] = cx + sin((float)(idx*rads + 72) * deg_to_rad) * diameter; + Ypoints_O[idx] = cy + cos((float)(idx*rads + 72) * deg_to_rad) * diameter; + // makes the inner points + Xpoints_I[idx] = cx + sin((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor); + // 36 is half of 72, and this will allow the inner and outer points to line up like a triangle. + Ypoints_I[idx] = cy + cos((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor); + } + + for(int idx = 0; idx < sides; idx++) { + if((idx+1) < sides) { + if(fill) {// this part below should be self explanatory. It fills in the star. + _fillTriangle(cx,cy,Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color); + _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color); + } + else { + _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color); + _drawLine(Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color); + } + } + else { + if(fill) { + _fillTriangle(cx,cy,Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color); + _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color); + } + else { + _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color); + _drawLine(Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color); + } + } + } +} +*/ + +// ================ Font and string functions ================================== + +//-------------------------------------------------------- +static int load_file_font(const char * fontfile, int info) +{ + int err = 0; + char err_msg[256] = {'\0'}; + + if (userfont != NULL) { + free(userfont); + userfont = NULL; + } + + struct stat sb; + + // Open the file + FILE *fhndl = fopen(fontfile, "r"); + if (!fhndl) { + sprintf(err_msg, "Error opening font file '%s'", fontfile); + err = 1; + goto exit; + } + + // Get file size + if (stat(fontfile, &sb) != 0) { + sprintf(err_msg, "Error getting font file size"); + err = 2; + goto exit; + } + int fsize = sb.st_size; + if (fsize < 30) { + sprintf(err_msg, "Error getting font file size"); + err = 3; + goto exit; + } + + userfont = malloc(fsize+4); + if (userfont == NULL) { + sprintf(err_msg, "Font memory allocation error"); + fclose(fhndl); + err = 4; + goto exit; + } + + int read = fread(userfont, 1, fsize, fhndl); + + fclose(fhndl); + + if (read != fsize) { + sprintf(err_msg, "Font read error"); + err = 5; + goto exit; + } + + userfont[read] = 0; + if (strstr((char *)(userfont+read-8), "RPH_font") == NULL) { + sprintf(err_msg, "Font ID not found"); + err = 6; + goto exit; + } + + // Check size + int size = 0; + int numchar = 0; + int width = userfont[0]; + int height = userfont[1]; + uint8_t first = 255; + uint8_t last = 0; + //int offst = 0; + int pminwidth = 255; + int pmaxwidth = 0; + + if (width != 0) { + // Fixed font + numchar = userfont[3]; + first = userfont[2]; + last = first + numchar - 1; + size = ((width * height * numchar) / 8) + 4; + } + else { + // Proportional font + size = 4; // point at first char data + uint8_t charCode; + int charwidth; + + do { + charCode = userfont[size]; + charwidth = userfont[size+2]; + + if (charCode != 0xFF) { + numchar++; + if (charwidth != 0) size += ((((charwidth * userfont[size+3])-1) / 8) + 7); + else size += 6; + + if (info) { + if (charwidth > pmaxwidth) pmaxwidth = charwidth; + if (charwidth < pminwidth) pminwidth = charwidth; + if (charCode < first) first = charCode; + if (charCode > last) last = charCode; + } + } + else size++; + } while ((size < (read-8)) && (charCode != 0xFF)); + } + + if (size != (read-8)) { + sprintf(err_msg, "Font size error: found %d expected %d)", size, (read-8)); + err = 7; + goto exit; + } + + if (info) { + if (width != 0) { + printf("Fixed width font:\r\n size: %d width: %d height: %d characters: %d (%d~%d)", + size, width, height, numchar, first, last); + } + else { + printf("Proportional font:\r\n size: %d width: %d~%d height: %d characters: %d (%d~%d)\n", + size, pminwidth, pmaxwidth, height, numchar, first, last); + } + } + +exit: + if (err) { + if (userfont) { + free(userfont); + userfont = NULL; + } + if (info) printf("Error: %d [%s]\r\n", err, err_msg); + } + return err; +} + +//------------------------------------------------ +int compile_font_file(char *fontfile, uint8_t dbg) +{ + int err = 0; + char err_msg[128] = {'\0'}; + char outfile[128] = {'\0'}; + size_t len; + struct stat sb; + FILE *ffd = NULL; + FILE *ffd_out = NULL; + char *sourcebuf = NULL; + + len = strlen(fontfile); + + // check here that filename end with ".c". + if ((len < 3) || (len > 125) || (strcmp(fontfile + len - 2, ".c") != 0)) { + sprintf(err_msg, "not a .c file"); + err = 1; + goto exit; + } + + sprintf(outfile, "%s", fontfile); + sprintf(outfile+strlen(outfile)-1, "fon"); + + // Open the source file + if (stat(fontfile, &sb) != 0) { + sprintf(err_msg, "Error opening source file '%s'", fontfile); + err = 2; + goto exit; + } + // Open the file + ffd = fopen(fontfile, "rb"); + if (!ffd) { + sprintf(err_msg, "Error opening source file '%s'", fontfile); + err = 3; + goto exit; + } + + // Open the font file + ffd_out= fopen(outfile, "wb"); + if (!ffd_out) { + sprintf(err_msg, "error opening destination file"); + err = 4; + goto exit; + } + + // Get file size + int fsize = sb.st_size; + if (fsize <= 0) { + sprintf(err_msg, "source file size error"); + err = 5; + goto exit; + } + + sourcebuf = malloc(fsize+4); + if (sourcebuf == NULL) { + sprintf(err_msg, "memory allocation error"); + err = 6; + goto exit; + } + char *fbuf = sourcebuf; + + int rdsize = fread(fbuf, 1, fsize, ffd); + fclose(ffd); + ffd = NULL; + + if (rdsize != fsize) { + sprintf(err_msg, "error reading from source file"); + err = 7; + goto exit; + } + + *(fbuf+rdsize) = '\0'; + + fbuf = strchr(fbuf, '{'); // beginning of font data + char *fend = strstr(fbuf, "};"); // end of font data + + if ((fbuf == NULL) || (fend == NULL) || ((fend-fbuf) < 22)) { + sprintf(err_msg, "wrong source file format"); + err = 8; + goto exit; + } + + fbuf++; + *fend = '\0'; + char hexstr[5] = {'\0'}; + int lastline = 0; + + fbuf = strstr(fbuf, "0x"); + int size = 0; + char *nextline; + char *numptr; + + int bptr = 0; + + while ((fbuf != NULL) && (fbuf < fend) && (lastline == 0)) { + nextline = strchr(fbuf, '\n'); // beginning of the next line + if (nextline == NULL) { + nextline = fend-1; + lastline++; + } + else nextline++; + + while (fbuf < nextline) { + numptr = strstr(fbuf, "0x"); + if ((numptr == NULL) || ((fbuf+4) > nextline)) numptr = strstr(fbuf, "0X"); + if ((numptr != NULL) && ((numptr+4) <= nextline)) { + fbuf = numptr; + if (bptr >= 128) { + // buffer full, write to file + if (fwrite(outfile, 1, 128, ffd_out) != 128) goto error; + bptr = 0; + size += 128; + } + memcpy(hexstr, fbuf, 4); + hexstr[4] = 0; + outfile[bptr++] = (uint8_t)strtol(hexstr, NULL, 0); + fbuf += 4; + } + else fbuf = nextline; + } + fbuf = nextline; + } + + if (bptr > 0) { + size += bptr; + if (fwrite(outfile, 1, bptr, ffd_out) != bptr) goto error; + } + + // write font ID + sprintf(outfile, "RPH_font"); + if (fwrite(outfile, 1, 8, ffd_out) != 8) goto error; + + // === Test compiled font === + sprintf(outfile, "%s", fontfile); + sprintf(outfile+strlen(outfile)-1, "fon"); + + uint8_t *uf = userfont; // save userfont pointer + userfont = NULL; + if (load_file_font(outfile, 1) == 0) { + sprintf(err_msg, "Error compiling file!"); + } + else { + free(userfont); + sprintf(err_msg, "File compiled successfully."); + } + userfont = uf; // restore userfont + + goto exit; + +error: + sprintf(err_msg, "error writing to destination file"); + err = 9; + +exit: + if (sourcebuf) free(sourcebuf); + if (ffd) fclose(ffd); + if (ffd_out) fclose(ffd_out); + + if (dbg) printf("%s\r\n", err_msg); + + return err; +} + + +// ----------------------------------------------------------------------------------------- +// Individual Proportional Font Character Format: +// ----------------------------------------------------------------------------------------- +// Character Code +// yOffset (start Y of visible pixels) +// Width (width of the visible pixels) +// Height (height of the visible pixels) +// xOffset (start X of visible pixels) +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] +// ----------------------------------------------------------------------------------------- + +//--------------------------------------------------------------------------------------------- +// Character drawing rectangle is (0, 0) (xDelta-1, cfont.y_size-1) +// Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1) +//--------------------------------------------------------------------------------------------- + +//---------------------------------- +void getFontCharacters(uint8_t *buf) +{ + if (cfont.bitmap == 2) { + //For 7 segment font only characters 0,1,2,3,4,5,6,7,8,9, . , - , : , / are available. + for (uint8_t n=0; n < 11; n++) { + buf[n] = n + 0x30; + } + buf[11] = '.'; + buf[12] = '-'; + buf[13] = '/'; + buf[14] = '\0'; + return; + } + + if (cfont.x_size > 0) { + for (uint8_t n=0; n < cfont.numchars; n++) { + buf[n] = cfont.offset + n; + } + buf[cfont.numchars] = '\0'; + return; + } + + uint16_t tempPtr = 4; // point at first char data + uint8_t cc, cw, ch, n; + + n = 0; + cc = cfont.font[tempPtr++]; + while (cc != 0xFF) { + cfont.numchars++; + tempPtr++; + cw = cfont.font[tempPtr++]; + ch = cfont.font[tempPtr++]; + tempPtr++; + tempPtr++; + if (cw != 0) { + // packed bits + tempPtr += (((cw * ch)-1) / 8) + 1; + } + buf[n++] = cc; + cc = cfont.font[tempPtr++]; + } + buf[n] = '\0'; +} + +// Set max width & height of the proportional font +//----------------------------- +static void getMaxWidthHeight() +{ + uint16_t tempPtr = 4; // point at first char data + uint8_t cc, cw, ch, cd, cy; + + cfont.numchars = 0; + cfont.max_x_size = 0; + + cc = cfont.font[tempPtr++]; + while (cc != 0xFF) { + cfont.numchars++; + cy = cfont.font[tempPtr++]; + cw = cfont.font[tempPtr++]; + ch = cfont.font[tempPtr++]; + tempPtr++; + cd = cfont.font[tempPtr++]; + cy += ch; + if (cw > cfont.max_x_size) cfont.max_x_size = cw; + if (cd > cfont.max_x_size) cfont.max_x_size = cd; + if (ch > cfont.y_size) cfont.y_size = ch; + if (cy > cfont.y_size) cfont.y_size = cy; + if (cw != 0) { + // packed bits + tempPtr += (((cw * ch)-1) / 8) + 1; + } + cc = cfont.font[tempPtr++]; + } + cfont.size = tempPtr; +} + +// Return the Glyph data for an individual character in the proportional font +//------------------------------------ +static uint8_t getCharPtr(uint8_t c) { + uint16_t tempPtr = 4; // point at first char data + + do { + fontChar.charCode = cfont.font[tempPtr++]; + if (fontChar.charCode == 0xFF) return 0; + + fontChar.adjYOffset = cfont.font[tempPtr++]; + fontChar.width = cfont.font[tempPtr++]; + fontChar.height = cfont.font[tempPtr++]; + fontChar.xOffset = cfont.font[tempPtr++]; + fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset); + fontChar.xDelta = cfont.font[tempPtr++]; + + if (c != fontChar.charCode && fontChar.charCode != 0xFF) { + if (fontChar.width != 0) { + // packed bits + tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1; + } + } + } while ((c != fontChar.charCode) && (fontChar.charCode != 0xFF)); + + fontChar.dataPtr = tempPtr; + if (c == fontChar.charCode) { + if (font_forceFixed > 0) { + // fix width & offset for forced fixed width + fontChar.xDelta = cfont.max_x_size; + fontChar.xOffset = (fontChar.xDelta - fontChar.width) / 2; + } + } + else return 0; + + return 1; +} + +/* +//----------------------- +static void _testFont() { + if (cfont.x_size) { + printf("FONT TEST: fixed font\r\n"); + return; + } + uint16_t tempPtr = 4; // point at first char data + uint8_t c = 0x20; + for (c=0x20; c <0xFF; c++) { + fontChar.charCode = cfont.font[tempPtr++]; + if (fontChar.charCode == 0xFF) break; + if (fontChar.charCode != c) { + printf("FONT TEST: last sequential char: %d, expected %d\r\n", fontChar.charCode, c); + break; + } + c = fontChar.charCode; + fontChar.adjYOffset = cfont.font[tempPtr++]; + fontChar.width = cfont.font[tempPtr++]; + fontChar.height = cfont.font[tempPtr++]; + fontChar.xOffset = cfont.font[tempPtr++]; + fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset); + fontChar.xDelta = cfont.font[tempPtr++]; + + if (fontChar.charCode != 0xFF) { + if (fontChar.width != 0) { + // packed bits + tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1; + } + } + } + printf("FONT TEST: W=%d H=%d last char: %d [%c]; length: %d\r\n", cfont.max_x_size, cfont.y_size, c, c, tempPtr); +} +*/ + +//=================================================== +void EPD_setFont(uint8_t font, const char *font_file) +{ + cfont.font = NULL; + + if (font == FONT_7SEG) { + cfont.bitmap = 2; + cfont.x_size = 24; + cfont.y_size = 6; + cfont.offset = 0; + cfont.color = _fg; + } + else { + if (font == USER_FONT) { + if (load_file_font(font_file, 0) != 0) cfont.font = tft_DefaultFont; + else cfont.font = userfont; + } + else if (font == DEJAVU18_FONT) cfont.font = tft_Dejavu18; + else if (font == DEJAVU24_FONT) cfont.font = tft_Dejavu24; + else if (font == UBUNTU16_FONT) cfont.font = tft_Ubuntu16; + else if (font == COMIC24_FONT) cfont.font = tft_Comic24; + else if (font == MINYA24_FONT) cfont.font = tft_minya24; + else if (font == TOONEY32_FONT) cfont.font = tft_tooney32; + else if (font == SMALL_FONT) cfont.font = tft_SmallFont; + else cfont.font = tft_DefaultFont; + + cfont.bitmap = 1; + cfont.x_size = cfont.font[0]; + cfont.y_size = cfont.font[1]; + if (cfont.x_size > 0) { + cfont.offset = cfont.font[2]; + cfont.numchars = cfont.font[3]; + cfont.size = cfont.x_size * cfont.y_size * cfont.numchars; + } + else { + cfont.offset = 4; + getMaxWidthHeight(); + } + //_testFont(); + } +} + +// ----------------------------------------------------------------------------------------- +// Individual Proportional Font Character Format: +// ----------------------------------------------------------------------------------------- +// Character Code +// yOffset (start Y of visible pixels) +// Width (width of the visible pixels) +// Height (height of the visible pixels) +// xOffset (start X of visible pixels) +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] +// ----------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +// Character drawing rectangle is (0, 0) (xDelta-1, cfont.y_size-1) +// Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1) +//--------------------------------------------------------------------------------------------- + +// print non-rotated proportional character +// character is already in fontChar +//---------------------------------------------- +static int printProportionalChar(int x, int y) { + uint8_t ch = 0; + int i, j, char_width; + + char_width = ((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta); + int cx, cy; + + if (!font_transparent) _fillRect(x, y, char_width+1, cfont.y_size, _bg); + + // draw Glyph + uint8_t mask = 0x80; + for (j=0; j < fontChar.height; j++) { + for (i=0; i < fontChar.width; i++) { + if (((i + (j*fontChar.width)) % 8) == 0) { + mask = 0x80; + ch = cfont.font[fontChar.dataPtr++]; + } + + if ((ch & mask) !=0) { + cx = (uint16_t)(x+fontChar.xOffset+i); + cy = (uint16_t)(y+j+fontChar.adjYOffset); + _drawPixel(cx, cy, _fg); + } + mask >>= 1; + } + } + + return char_width; +} + +// non-rotated fixed width character +//---------------------------------------------- +static void printChar(uint8_t c, int x, int y) { + uint8_t i, j, ch, fz, mask; + uint16_t k, temp, cx, cy; + + // fz = bytes per char row + fz = cfont.x_size/8; + if (cfont.x_size % 8) fz++; + + // get character position in buffer + temp = ((c-cfont.offset)*((fz)*cfont.y_size))+4; + + if (!font_transparent) _fillRect(x, y, cfont.x_size, cfont.y_size, _bg); + + for (j=0; j>= 1; + } + } + temp += (fz); + } +} + +// print rotated proportional character +// character is already in fontChar +//--------------------------------------------------- +static int rotatePropChar(int x, int y, int offset) { + uint8_t ch = 0; + double radian = font_rotate * DEG_TO_RAD; + float cos_radian = cos(radian); + float sin_radian = sin(radian); + + uint8_t mask = 0x80; + for (int j=0; j < fontChar.height; j++) { + for (int i=0; i < fontChar.width; i++) { + if (((i + (j*fontChar.width)) % 8) == 0) { + mask = 0x80; + ch = cfont.font[fontChar.dataPtr++]; + } + + int newX = (int)(x + (((offset + i) * cos_radian) - ((j+fontChar.adjYOffset)*sin_radian))); + int newY = (int)(y + (((j+fontChar.adjYOffset) * cos_radian) + ((offset + i) * sin_radian))); + + if ((ch & mask) != 0) _drawPixel(newX,newY,_fg); + else if (!font_transparent) _drawPixel(newX,newY,_bg); + + mask >>= 1; + } + } + + return fontChar.xDelta+1; +} + +// rotated fixed width character +//-------------------------------------------------------- +static void rotateChar(uint8_t c, int x, int y, int pos) { + uint8_t i,j,ch,fz,mask; + uint16_t temp; + int newx,newy; + double radian = font_rotate*0.0175; + float cos_radian = cos(radian); + float sin_radian = sin(radian); + int zz; + + if( cfont.x_size < 8 ) fz = cfont.x_size; + else fz = cfont.x_size/8; + temp=((c-cfont.offset)*((fz)*cfont.y_size))+4; + + for (j=0; j>= 1; + } + } + temp+=(fz); + } + // calculate x,y for the next char + EPD_X = (int)(x + ((pos+1) * cfont.x_size * cos_radian)); + EPD_Y = (int)(y + ((pos+1) * cfont.x_size * sin_radian)); +} + +//---------------------- +static int _7seg_width() +{ + return (2 * (2 * cfont.y_size + 1)) + cfont.x_size; +} + +//----------------------- +static int _7seg_height() +{ + return (3 * (2 * cfont.y_size + 1)) + (2 * cfont.x_size); +} + +// Returns the string width in pixels. +// Useful for positions strings on the screen. +//=============================== +int EPD_getStringWidth(char* str) +{ + int strWidth = 0; + + if (cfont.bitmap == 2) strWidth = ((_7seg_width()+2) * strlen(str)) - 2; // 7-segment font + else if (cfont.x_size != 0) strWidth = strlen(str) * cfont.x_size; // fixed width font + else { + // calculate the width of the string of proportional characters + char* tempStrptr = str; + while (*tempStrptr != 0) { + if (getCharPtr(*tempStrptr++)) { + strWidth += (((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta) + 1); + } + } + strWidth--; + } + return strWidth; +} + +//=============================================== +void EPD_clearStringRect(int x, int y, char *str) +{ + int w = EPD_getStringWidth(str); + int h = EPD_getfontheight(); + EPD_fillRect(x+dispWin.x1, y+dispWin.y1, w, h, _bg); +} + +//============================================================================== +/** + * bit-encoded bar position of all digits' bcd segments + * + * 6 + * +-----+ + * 3 | . | 2 + * +--5--+ + * 1 | . | 0 + * +--.--+ + * 4 + */ +static const uint16_t font_bcd[] = { + 0x200, // 0010 0000 0000 // - + 0x080, // 0000 1000 0000 // . + 0x06C, // 0100 0110 1100 // /, degree + 0x05f, // 0000 0101 1111, // 0 + 0x005, // 0000 0000 0101, // 1 + 0x076, // 0000 0111 0110, // 2 + 0x075, // 0000 0111 0101, // 3 + 0x02d, // 0000 0010 1101, // 4 + 0x079, // 0000 0111 1001, // 5 + 0x07b, // 0000 0111 1011, // 6 + 0x045, // 0000 0100 0101, // 7 + 0x07f, // 0000 0111 1111, // 8 + 0x07d, // 0000 0111 1101 // 9 + 0x900 // 1001 0000 0000 // : +}; + +//----------------------------------------------------------------------------------------------- +static void barVert(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) { + _fillTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, color); + _fillTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, color); + _fillRect(x, y+2*w+1, 2*w+1, l, color); + if (cfont.offset) { + _drawTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, outline); + _drawTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, outline); + _drawRect(x, y+2*w+1, 2*w+1, l, outline); + } +} + +//---------------------------------------------------------------------------------------------- +static void barHor(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) { + _fillTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, color); + _fillTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, color); + _fillRect(x+2*w+1, y, l, 2*w+1, color); + if (cfont.offset) { + _drawTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, outline); + _drawTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, outline); + _drawRect(x+2*w+1, y, l, 2*w+1, outline); + } +} + +//-------------------------------------------------------------------------------------------- +static void _draw7seg(int16_t x, int16_t y, int8_t num, int16_t w, int16_t l, color_t color) { + /* TODO: clipping */ + if (num < 0x2D || num > 0x3A) return; + + int16_t c = font_bcd[num-0x2D]; + int16_t d = 2*w+l+1; + + // === Clear unused segments === + /* + if (!(c & 0x001)) barVert(x+d, y+d, w, l, _bg, _bg); + if (!(c & 0x002)) barVert(x, y+d, w, l, _bg, _bg); + if (!(c & 0x004)) barVert(x+d, y, w, l, _bg, _bg); + if (!(c & 0x008)) barVert(x, y, w, l, _bg, _bg); + if (!(c & 0x010)) barHor(x, y+2*d, w, l, _bg, _bg); + if (!(c & 0x020)) barHor(x, y+d, w, l, _bg, _bg); + if (!(c & 0x040)) barHor(x, y, w, l, _bg, _bg); + + if (!(c & 0x080)) { + // low point + _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, _bg); + if (cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, _bg); + } + if (!(c & 0x100)) { + // down middle point + _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, _bg); + if (cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, _bg); + } + if (!(c & 0x800)) { + // up middle point + _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, _bg); + if (cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, _bg); + } + if (!(c & 0x200)) { + // middle, minus + _fillRect(x+2*w+1, y+d, l, 2*w+1, _bg); + if (cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, _bg); + } + */ + barVert(x+d, y+d, w, l, _bg, _bg); + barVert(x, y+d, w, l, _bg, _bg); + barVert(x+d, y, w, l, _bg, _bg); + barVert(x, y, w, l, _bg, _bg); + barHor(x, y+2*d, w, l, _bg, _bg); + barHor(x, y+d, w, l, _bg, _bg); + barHor(x, y, w, l, _bg, _bg); + + _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, _bg); + _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, _bg); + _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, _bg); + _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, _bg); + _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, _bg); + _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, _bg); + _fillRect(x+2*w+1, y+d, l, 2*w+1, _bg); + _drawRect(x+2*w+1, y+d, l, 2*w+1, _bg); + + // === Draw used segments === + if (c & 0x001) barVert(x+d, y+d, w, l, color, cfont.color); // down right + if (c & 0x002) barVert(x, y+d, w, l, color, cfont.color); // down left + if (c & 0x004) barVert(x+d, y, w, l, color, cfont.color); // up right + if (c & 0x008) barVert(x, y, w, l, color, cfont.color); // up left + if (c & 0x010) barHor(x, y+2*d, w, l, color, cfont.color); // down + if (c & 0x020) barHor(x, y+d, w, l, color, cfont.color); // middle + if (c & 0x040) barHor(x, y, w, l, color, cfont.color); // up + + if (c & 0x080) { + // low point + _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, color); + if (cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, cfont.color); + } + if (c & 0x100) { + // down middle point + _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, color); + if (cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, cfont.color); + } + if (c & 0x800) { + // up middle point + _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, color); + if (cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, cfont.color); + } + if (c & 0x200) { + // middle, minus + _fillRect(x+2*w+1, y+d, l, 2*w+1, color); + if (cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, cfont.color); + } +} +//============================================================================== + +//====================================== +void EPD_print(char *st, int x, int y) { + int stl, i, tmpw, tmph, fh; + uint8_t ch; + + if (cfont.bitmap == 0) return; // wrong font selected + + // ** Rotated strings cannot be aligned + if ((font_rotate != 0) && ((x <= CENTER) || (y <= CENTER))) return; + + if ((x < LASTX) || (font_rotate == 0)) EPD_OFFSET = 0; + + if ((x >= LASTX) && (x < LASTY)) x = EPD_X + (x-LASTX); + else if (x > CENTER) x += dispWin.x1; + + if (y >= LASTY) y = EPD_Y + (y-LASTY); + else if (y > CENTER) y += dispWin.y1; + + // ** Get number of characters in string to print + stl = strlen(st); + + // ** Calculate CENTER, RIGHT or BOTTOM position + tmpw = EPD_getStringWidth(st); // string width in pixels + fh = cfont.y_size; // font height + if ((cfont.x_size != 0) && (cfont.bitmap == 2)) { + // 7-segment font + fh = (3 * (2 * cfont.y_size + 1)) + (2 * cfont.x_size); // 7-seg character height + } + + if (x == RIGHT) x = dispWin.x2 - tmpw + dispWin.x1; + else if (x == CENTER) x = (((dispWin.x2 - dispWin.x1 + 1) - tmpw) / 2) + dispWin.x1; + + if (y == BOTTOM) y = dispWin.y2 - fh + dispWin.y1; + else if (y==CENTER) y = (((dispWin.y2 - dispWin.y1 + 1) - (fh/2)) / 2) + dispWin.y1; + + if (x < dispWin.x1) x = dispWin.x1; + if (y < dispWin.y1) y = dispWin.y1; + if ((x > dispWin.x2) || (y > dispWin.y2)) return; + + EPD_X = x; + EPD_Y = y; + + // ** Adjust y position + tmph = cfont.y_size; // font height + // for non-proportional fonts, char width is the same for all chars + tmpw = cfont.x_size; + if (cfont.x_size != 0) { + if (cfont.bitmap == 2) { // 7-segment font + tmpw = _7seg_width(); // character width + tmph = _7seg_height(); // character height + } + } + else EPD_OFFSET = 0; // fixed font; offset not needed + + if (( EPD_Y + tmph - 1) > dispWin.y2) return; + + int offset = EPD_OFFSET; + + for (i=0; i (dispWin.y2-tmph)) break; + EPD_X = dispWin.x1; + } + } + + else { // ==== other characters ==== + if (cfont.x_size == 0) { + // for proportional font get character data to 'fontChar' + if (getCharPtr(ch)) tmpw = fontChar.xDelta; + else continue; + } + + // check if character can be displayed in the current line + if (( EPD_X+tmpw) > (dispWin.x2)) { + if (text_wrap == 0) break; + EPD_Y += tmph + font_line_space; + if ( EPD_Y > (dispWin.y2-tmph)) break; + EPD_X = dispWin.x1; + } + + // Let's print the character + if (cfont.x_size == 0) { + // == proportional font + if (font_rotate == 0) EPD_X += printProportionalChar( EPD_X, EPD_Y) + 1; + else { + // rotated proportional font + offset += rotatePropChar(x, y, offset); + EPD_OFFSET = offset; + } + } + else { + if (cfont.bitmap == 1) { + // == fixed font + if ((ch < cfont.offset) || ((ch-cfont.offset) > cfont.numchars)) ch = cfont.offset; + if (font_rotate == 0) { + printChar(ch, EPD_X, EPD_Y); + EPD_X += tmpw; + } + else rotateChar(ch, x, y, i); + } + else if (cfont.bitmap == 2) { + // == 7-segment font == + _draw7seg( EPD_X, EPD_Y, ch, cfont.y_size, cfont.x_size, _fg); + EPD_X += (tmpw + 2); + } + } + } + } +} + + +// ================ Service functions ========================================== + +//===================================================================== +void EPD_setclipwin(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) +{ + dispWin.x1 = x1; + dispWin.y1 = y1; + dispWin.x2 = x2; + dispWin.y2 = y2; + + if (dispWin.x2 >= EPD_DISPLAY_WIDTH) dispWin.x2 = EPD_DISPLAY_WIDTH-1; + if (dispWin.y2 >= EPD_DISPLAY_HEIGHT) dispWin.y2 = EPD_DISPLAY_HEIGHT-1; + if (dispWin.x1 > dispWin.x2) dispWin.x1 = dispWin.x2; + if (dispWin.y1 > dispWin.y2) dispWin.y1 = dispWin.y2; +} + +//===================== +void EPD_resetclipwin() +{ + dispWin.x2 = EPD_DISPLAY_WIDTH-1; + dispWin.y2 = EPD_DISPLAY_HEIGHT-1; + dispWin.x1 = 0; + dispWin.y1 = 0; +} + +//========================================================================== +void set_7seg_font_atrib(uint8_t l, uint8_t w, int outline, color_t color) { + if (cfont.bitmap != 2) return; + + if (l < 6) l = 6; + if (l > 40) l = 40; + if (w < 1) w = 1; + if (w > (l/2)) w = l/2; + if (w > 12) w = 12; + + cfont.x_size = l; + cfont.y_size = w; + cfont.offset = outline; + cfont.color = color; +} + +//========================================== +int EPD_getfontsize(int *width, int* height) +{ + if (cfont.bitmap == 1) { + if (cfont.x_size != 0) *width = cfont.x_size; // fixed width font + else *width = cfont.max_x_size; // proportional font + *height = cfont.y_size; + } + else if (cfont.bitmap == 2) { + // 7-segment font + *width = _7seg_width(); + *height = _7seg_height(); + } + else { + *width = 0; + *height = 0; + return 0; + } + return 1; +} + +//===================== +int EPD_getfontheight() +{ + if (cfont.bitmap == 1) return cfont.y_size; // Bitmap font + else if (cfont.bitmap == 2) return _7seg_height(); // 7-segment font + return 0; +} + +//==================== +void EPD_saveClipWin() +{ + dispWinTemp.x1 = dispWin.x1; + dispWinTemp.y1 = dispWin.y1; + dispWinTemp.x2 = dispWin.x2; + dispWinTemp.y2 = dispWin.y2; +} + +//======================= +void EPD_restoreClipWin() +{ + dispWin.x1 = dispWinTemp.x1; + dispWin.y1 = dispWinTemp.y1; + dispWin.x2 = dispWinTemp.x2; + dispWin.y2 = dispWinTemp.y2; +} + + +// ================ JPG SUPPORT ================================================ + +// RGB to GRAYSCALE constants +// 0.2989 0.5870 0.1140 +#define GS_FACT_R 0.2989 +#define GS_FACT_G 0.4870 +#define GS_FACT_B 0.2140 + +// User defined device identifier +typedef struct { + FILE *fhndl; // File handler for input function + int x; // image top left point X position + int y; // image top left point Y position + uint8_t *membuff; // memory buffer containing the image + uint32_t bufsize; // size of the memory buffer + uint32_t bufptr; // memory buffer current position +} JPGIODEV; + + +// User defined call-back function to input JPEG data from file +//--------------------- +static UINT tjd_input ( + JDEC* jd, // Decompression object + BYTE* buff, // Pointer to the read buffer (NULL:skip) + UINT nd // Number of bytes to read/skip from input stream +) +{ + int rb = 0; + // Device identifier for the session (5th argument of jd_prepare function) + JPGIODEV *dev = (JPGIODEV*)jd->device; + + if (buff) { // Read nd bytes from the input strem + rb = fread(buff, 1, nd, dev->fhndl); + return rb; // Returns actual number of bytes read + } + else { // Remove nd bytes from the input stream + if (fseek(dev->fhndl, nd, SEEK_CUR) >= 0) return nd; + else return 0; + } +} + +// User defined call-back function to input JPEG data from memory buffer +//------------------------- +static UINT tjd_buf_input ( + JDEC* jd, // Decompression object + BYTE* buff, // Pointer to the read buffer (NULL:skip) + UINT nd // Number of bytes to read/skip from input stream +) +{ + // Device identifier for the session (5th argument of jd_prepare function) + JPGIODEV *dev = (JPGIODEV*)jd->device; + if (!dev->membuff) return 0; + if (dev->bufptr >= (dev->bufsize + 2)) return 0; // end of stream + + if ((dev->bufptr + nd) > (dev->bufsize + 2)) nd = (dev->bufsize + 2) - dev->bufptr; + + if (buff) { // Read nd bytes from the input strem + memcpy(buff, dev->membuff + dev->bufptr, nd); + dev->bufptr += nd; + return nd; // Returns number of bytes read + } + else { // Remove nd bytes from the input stream + dev->bufptr += nd; + return nd; + } +} + +// User defined call-back function to output RGB bitmap to display device +//---------------------- +static UINT tjd_output ( + JDEC* jd, // Decompression object of current session + void* bitmap, // Bitmap data to be output + JRECT* rect // Rectangular region to output +) +{ + // Device identifier for the session (5th argument of jd_prepare function) + JPGIODEV *dev = (JPGIODEV*)jd->device; + + // ** Put the rectangular into the display device ** + int x; + int y; + int dleft, dtop, dright, dbottom; + BYTE *src = (BYTE*)bitmap; + + int left = rect->left + dev->x; + int top = rect->top + dev->y; + int right = rect->right + dev->x; + int bottom = rect->bottom + dev->y; + + if ((left > dispWin.x2) || (top > dispWin.y2)) return 1; // out of screen area, return + if ((right < dispWin.x1) || (bottom < dispWin.y1)) return 1;// out of screen area, return + + if (left < dispWin.x1) dleft = dispWin.x1; + else dleft = left; + if (top < dispWin.y1) dtop = dispWin.y1; + else dtop = top; + if (right > dispWin.x2) dright = dispWin.x2; + else dright = right; + if (bottom > dispWin.y2) dbottom = dispWin.y2; + else dbottom = bottom; + + if ((dleft > dispWin.x2) || (dtop > dispWin.y2)) return 1; // out of screen area, return + if ((dright < dispWin.x1) || (dbottom < dispWin.y1)) return 1; // out of screen area, return + + uint32_t len = ((dright-dleft+1) * (dbottom-dtop+1)); // calculate length of data + + float gs_clr = 0; + uint8_t rgb_color[3]; + uint8_t last_lvl, i; + uint8_t pix; + if ((len > 0) && (len <= JPG_IMAGE_LINE_BUF_SIZE)) { + for (y = top; y <= bottom; y++) { + for (x = left; x <= right; x++) { + // Clip to display area + if ((x >= dleft) && (y >= dtop) && (x <= dright) && (y <= dbottom)) { + // Directly convert color to 4-bit gray scale + pix = 0; + pix |= ((*src++) >> 4) & 0x08; + pix |= ((*src++) >> 5) & 0x06; + pix |= ((*src++) >> 7); + pix ^= 0x0F; + + + /* Convert rgb color to gray scale + memcpy(rgb_color, src, 3); + src += 3; + gs_clr = (GS_FACT_R * rgb_color[0]) + (GS_FACT_G * rgb_color[1]) + (GS_FACT_B * rgb_color[2]); + if (gs_clr > 255) gs_clr = 255; + // Use only 4 bits & invert + //pix = ((uint8_t)gs_clr >> 4) ^ 0x0F; + pix = (uint8_t)gs_clr; + + // Using gray scale lookup table + last_lvl = 0; + i = 0; + for (i=0; i<16; i++) { + if ((pix > last_lvl) && (pix <= lvl_buf_jpg[i])) { + pix = 15 - i; + last_lvl = lvl_buf[i]; + break; + } + last_lvl = lvl_buf[i]; + } + */ + gs_disp_buffer[(y * EPD_DISPLAY_WIDTH) + x] = pix; + gs_used_shades |= (1 << pix); + } + else src += 3; // skip + } + } + } + else { + printf("Data size error: %d jpg: (%d,%d,%d,%d) disp: (%d,%d,%d,%d)\r\n", len, left,top,right,bottom, dleft,dtop,dright,dbottom); + return 0; // stop decompression + } + + return 1; // Continue to decompression +} + +// X & Y can be < 0 ! +//================================================================================= +int EPD_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size) +{ + JPGIODEV dev; + struct stat sb; + char *work = NULL; // Pointer to the working buffer (must be 4-byte aligned) + UINT sz_work = 3800; // Size of the working buffer (must be power of 2) + JDEC jd; // Decompression object (70 bytes) + JRESULT rc; + int res = -10; + + if (fname == NULL) { + // image from buffer + dev.fhndl = NULL; + dev.membuff = buf; + dev.bufsize = size; + dev.bufptr = 0; + } + else { + // image from file + dev.membuff = NULL; + dev.bufsize = 0; + dev.bufptr = 0; + + if (stat(fname, &sb) != 0) { + if (image_debug) printf("File error: %ss\r\n", strerror(errno)); + res = -11; + goto exit; + } + + dev.fhndl = fopen(fname, "r"); + if (!dev.fhndl) { + if (image_debug) printf("Error opening file: %s\r\n", strerror(errno)); + res = -12; + goto exit; + } + } + + if (scale > 3) scale = 3; + + work = malloc(sz_work); + if (work) { + if (dev.membuff) rc = jd_prepare(&jd, tjd_buf_input, (void *)work, sz_work, &dev); + else rc = jd_prepare(&jd, tjd_input, (void *)work, sz_work, &dev); + if (rc == JDR_OK) { + if (x == CENTER) x = ((dispWin.x2 - dispWin.x1 + 1 - (int)(jd.width >> scale)) / 2) + dispWin.x1; + else if (x == RIGHT) x = dispWin.x2 + 1 - (int)(jd.width >> scale); + + if (y == CENTER) y = ((dispWin.y2 - dispWin.y1 + 1 - (int)(jd.height >> scale)) / 2) + dispWin.y1; + else if (y == BOTTOM) y = dispWin.y2 + 1 - (int)(jd.height >> scale); + + if (x < ((dispWin.x2-1) * -1)) x = (dispWin.x2-1) * -1; + if (y < ((dispWin.y2-1)) * -1) y = (dispWin.y2-1) * -1; + if (x > (dispWin.x2-1)) x = dispWin.x2 - 1; + if (y > (dispWin.y2-1)) y = dispWin.y2-1; + + dev.x = x; + dev.y = y; + + // Start to decode the JPEG file + rc = jd_decomp(&jd, tjd_output, scale); + + if (rc != JDR_OK) { + if (image_debug) printf("jpg decompression error %d\r\n", rc); + res = rc * -1; + } + res = 0; + if (image_debug) printf("Jpg size: %dx%d, position; %d,%d, scale: %d, bytes used: %d\r\n", jd.width, jd.height, x, y, scale, jd.sz_pool); + } + else { + if (image_debug) printf("jpg prepare error %d\r\n", rc); + res = rc * -1; + } + } + else { + if (image_debug) printf("work buffer allocation error\r\n"); + res = -13; + } + +exit: + if (work) free(work); // free work buffer + if (dev.fhndl) fclose(dev.fhndl); // close input file + return res; +} + diff --git a/components/epaper/EPD.h b/components/epaper/EPD.h new file mode 100644 index 0000000..784f8d5 --- /dev/null +++ b/components/epaper/EPD.h @@ -0,0 +1,571 @@ +/* + * High level EPD functions + * Author: LoBo 06/2017, https://github/loboris + * + */ + +#ifndef _EPD_H_ +#define _EPD_H_ + +#include +#include "EPDspi.h" + +typedef uint8_t color_t; + +typedef struct { + uint16_t x1; + uint16_t y1; + uint16_t x2; + uint16_t y2; +} dispWin_t; + +typedef struct { + uint8_t *font; + uint8_t x_size; + uint8_t y_size; + uint8_t offset; + uint16_t numchars; + uint16_t size; + uint8_t max_x_size; + uint8_t bitmap; + color_t color; +} Font_t; + + + +//========================================================================================== +// ==== Global variables =================================================================== +//========================================================================================== +uint8_t orientation; // current screen orientation +uint16_t font_rotate; // current font font_rotate angle (0~395) +uint8_t font_transparent; // if not 0 draw fonts transparent +uint8_t font_forceFixed; // if not zero force drawing proportional fonts with fixed width +uint8_t font_buffered_char; +uint8_t font_line_space; // additional spacing between text lines; added to font height +uint8_t text_wrap; // if not 0 wrap long text to the new line, else clip +color_t _fg; // current foreground color for fonts +color_t _bg; // current background for non transparent fonts +dispWin_t dispWin; // display clip window +float _angleOffset; // angle offset for arc, polygon and line by angle functions + +Font_t cfont; // Current font structure +uint8_t image_debug; + +int EPD_X; // X position of the next character after EPD_print() function +int EPD_Y; // Y position of the next character after EPD_print() function +// ========================================================================================= + + +// Buffer is created during jpeg decode for sending data +// Total size of the buffer is 2 * (JPG_IMAGE_LINE_BUF_SIZE * 3) +// The size must be multiple of 256 bytes !! +#define JPG_IMAGE_LINE_BUF_SIZE 512 + +// --- Constants for ellipse function --- +#define EPD_ELLIPSE_UPPER_RIGHT 0x01 +#define EPD_ELLIPSE_UPPER_LEFT 0x02 +#define EPD_ELLIPSE_LOWER_LEFT 0x04 +#define EPD_ELLIPSE_LOWER_RIGHT 0x08 + +// Constants for Arc function +// number representing the maximum angle (e.g. if 100, then if you pass in start=0 and end=50, you get a half circle) +// this can be changed with setArcParams function at runtime +#define DEFAULT_ARC_ANGLE_MAX 360 +// rotational offset in degrees defining position of value 0 (-90 will put it at the top of circle) +// this can be changed with setAngleOffset function at runtime +#define DEFAULT_ANGLE_OFFSET -90 + +#define PI 3.14159265359 + +#define MIN_POLIGON_SIDES 3 +#define MAX_POLIGON_SIDES 60 + +// === Color names constants === +#define EPD_BLACK 15 +#define EPD_WHITE 0 + +// === Color invert constants === +#define INVERT_ON 1 +#define INVERT_OFF 0 + +// === Screen orientation constants === +#define LANDSCAPE_0 1 +#define LANDSCAPE_180 2 + +// === Special coordinates constants === +#define CENTER -9003 +#define RIGHT -9004 +#define BOTTOM -9004 + +#define LASTX 7000 +#define LASTY 8000 + +// === Embedded fonts constants === +#define DEFAULT_FONT 0 +#define DEJAVU18_FONT 1 +#define DEJAVU24_FONT 2 +#define UBUNTU16_FONT 3 +#define COMIC24_FONT 4 +#define MINYA24_FONT 5 +#define TOONEY32_FONT 6 +#define SMALL_FONT 7 +#define FONT_7SEG 8 +#define USER_FONT 9 // font will be read from file + + + +// ===== PUBLIC FUNCTIONS ========================================================================= + +/* + * Draw pixel at given x,y coordinates + * + * Params: + * x: horizontal position + * y: vertical position + * color: pixel color +*/ +//------------------------------------------------------ +void EPD_drawPixel(int16_t x, int16_t y, color_t color); + +/* + * Read pixel color value from display GRAM at given x,y coordinates + * + * Params: + * x: horizontal position + * y: vertical position + * + * Returns: + * pixel color at x,y +*/ +//------------------------------------------ +color_t EPD_readPixel(int16_t x, int16_t y); + +/* + * Draw vertical line at given x,y coordinates + * + * Params: + * x: horizontal start position + * y: vertical start position + * h: line height in pixels + * color: line color +*/ +//--------------------------------------------------------------------- +void EPD_drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color); + +/* + * Draw horizontal line at given x,y coordinates + * + * Params: + * x: horizontal start position + * y: vertical start position + * w: line width in pixels + * color: line color +*/ +//--------------------------------------------------------------------- +void EPD_drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color); + +/* + * Draw line on screen + * + * Params: + * x0: horizontal start position + * y0: vertical start position + * x1: horizontal end position + * y1: vertical end position + * color: line color +*/ +//------------------------------------------------------------------------------- +void EPD_drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color); + + +/* + * Draw line on screen from (x,y) point at given angle + * Line drawing angle starts at lower right quadrant of the screen and is offseted by + * '_angleOffset' global variable (default: -90 degrees) + * + * Params: + * x: horizontal start position + * y: vertical start position + * start: start offset from (x,y) + * len: length of the line + * angle: line angle in degrees + * color: line color +*/ +//----------------------------------------------------------------------------------------------------------- +void EPD_drawLineByAngle(uint16_t x, uint16_t y, uint16_t start, uint16_t len, uint16_t angle, color_t color); + +/* + * Fill given rectangular screen region with color + * + * Params: + * x: horizontal rect start position + * y: vertical rect start position + * w: rectangle width + * h: rectangle height + * color: fill color +*/ +//--------------------------------------------------------------------------- +void EPD_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color); + +/* + * Draw rectangle on screen + * + * Params: + * x: horizontal rect start position + * y: vertical rect start position + * w: rectangle width + * h: rectangle height + * color: rect line color +*/ +//------------------------------------------------------------------------------ +void EPD_drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color); + +/* + * Draw rectangle with rounded corners on screen + * + * Params: + * x: horizontal rect start position + * y: vertical rect start position + * w: rectangle width + * h: rectangle height + * r: corner radius + * color: rectangle color +*/ +//---------------------------------------------------------------------------------------------- +void EPD_drawRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color); + +/* + * Fill given rectangular screen region with rounded corners with color + * + * Params: + * x: horizontal rect start position + * y: vertical rect start position + * w: rectangle width + * h: rectangle height + * r: corner radius + * color: fill color +*/ +//---------------------------------------------------------------------------------------------- +void EPD_fillRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color); + +/* + * Fill the whole screen with color + * + * Params: + * color: fill color +*/ +//-------------------------------- +void EPD_fillScreen(color_t color); + +/* + * Fill the current clip window with color + * + * Params: + * color: fill color +*/ +//--------------------------------- +void EPD_fillWindow(color_t color); + +/* + * Draw triangle on screen + * + * Params: + * x0: first triangle point x position + * y0: first triangle point y position + * x0: second triangle point x position + * y0: second triangle point y position + * x0: third triangle point x position + * y0: third triangle point y position + * color: triangle color +*/ +//----------------------------------------------------------------------------------------------------------------- +void EPD_drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color); + +/* + * Fill triangular screen region with color + * + * Params: + * x0: first triangle point x position + * y0: first triangle point y position + * x0: second triangle point x position + * y0: second triangle point y position + * x0: third triangle point x position + * y0: third triangle point y position + * color: fill color +*/ +//----------------------------------------------------------------------------------------------------------------- +void EPD_fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color); + +/* + * Draw circle on screen + * + * Params: + * x: circle center x position + * y: circle center x position + * r: circle radius + * color: circle color +*/ +//------------------------------------------------------------------- +void EPD_drawCircle(int16_t x, int16_t y, int radius, color_t color); + +/* + * Fill circle on screen with color + * + * Params: + * x: circle center x position + * y: circle center x position + * r: circle radius + * color: circle fill color +*/ +//------------------------------------------------------------------- +void EPD_fillCircle(int16_t x, int16_t y, int radius, color_t color); + +/* + * Draw ellipse on screen + * + * Params: + * x0: ellipse center x position + * y0: ellipse center x position + * rx: ellipse horizontal radius + * ry: ellipse vertical radius + * option: drawing options, multiple options can be combined + 1 (TFT_ELLIPSE_UPPER_RIGHT) draw upper right corner + 2 (TFT_ELLIPSE_UPPER_LEFT) draw upper left corner + 4 (TFT_ELLIPSE_LOWER_LEFT) draw lower left corner + 8 (TFT_ELLIPSE_LOWER_RIGHT) draw lower right corner + to draw the whole ellipse use option value 15 (1 | 2 | 4 | 8) + * + * color: circle color +*/ +//------------------------------------------------------------------------------------------------------ +void EPD_drawEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option); + +/* + * Fill elliptical region on screen + * + * Params: + * x0: ellipse center x position + * y0: ellipse center x position + * rx: ellipse horizontal radius + * ry: ellipse vertical radius + * option: drawing options, multiple options can be combined + 1 (TFT_ELLIPSE_UPPER_RIGHT) fill upper right corner + 2 (TFT_ELLIPSE_UPPER_LEFT) fill upper left corner + 4 (TFT_ELLIPSE_LOWER_LEFT) fill lower left corner + 8 (TFT_ELLIPSE_LOWER_RIGHT) fill lower right corner + to fill the whole ellipse use option value 15 (1 | 2 | 4 | 8) + * + * color: fill color +*/ +//------------------------------------------------------------------------------------------------------ +void EPD_fillEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option); + + +/* + * Draw circle arc on screen + * Arc drawing angle starts at lower right quadrant of the screen and is offseted by + * '_angleOffset' global variable (default: -90 degrees) + * + * Params: + * cx: arc center X position + * cy: arc center Y position + * th: thickness of the drawn arc + * ry: arc vertical radius + * start: arc start angle in degrees + * end: arc end angle in degrees + * color: arc outline color + * fillcolor: arc fill color +*/ +//---------------------------------------------------------------------------------------------------------------------------- +void EPD_drawArc(uint16_t cx, uint16_t cy, uint16_t r, uint16_t th, float start, float end, color_t color, color_t fillcolor); + + +/* + * Draw polygon on screen + * + * Params: + * cx: polygon center X position + * cy: arc center Y position + * sides: number of polygon sides; MAX_POLIGON_SIDES ~ MAX_POLIGON_SIDES (3 ~ 60) + * diameter: diameter of the circle inside which the polygon is drawn + * color: polygon outline color + * fill: polygon fill color; if same as color, polygon is not filled + * deg: polygon rotation angle; 0 ~ 360 + * th: thickness of the polygon outline +*/ +//-------------------------------------------------------------------------------------------------------------- +void EPD_drawPolygon(int cx, int cy, int sides, int diameter, color_t color, color_t fill, int deg, uint8_t th); + + +//-------------------------------------------------------------------------------------- +//void EPD_drawStar(int cx, int cy, int diameter, color_t color, bool fill, float factor); + + +/* + * Set the font used for writing the text to display. + * + * ------------------------------------------------------------------------------------ + * For 7 segment font only characters 0,1,2,3,4,5,6,7,8,9, . , - , : , / are available. + * Character ‘/‘ draws the degree sign. + * ------------------------------------------------------------------------------------ + * + * Params: + * font: font number; use defined font names + * font_file: pointer to font file name; NULL for embeded fonts + */ +//---------------------------------------------------- +void EPD_setFont(uint8_t font, const char *font_file); + +/* + * Returns current font height & width in pixels. + * + * Params: + * width: pointer to returned font width + * height: pointer to returned font height + */ +//------------------------------------------- +int EPD_getfontsize(int *width, int* height); + + +/* + * Returns current font height in pixels. + * + */ +//---------------------- +int EPD_getfontheight(); + +/* + * Write text to display. + * + * Rotation of the displayed text depends on 'font_rotate' variable (0~360) + * if 'font_transparent' variable is set to 1, no background pixels will be printed + * + * If the text does not fit the screen width it will be clipped (if text_wrap=0), + * or continued on next line (if text_wrap=1) + * + * Two special characters are allowed in strings: + * ‘\r’ CR (0x0D), clears the display to EOL + * ‘\n’ LF (ox0A), continues to the new line, x=0 + * + * Params: + * st: pointer to null terminated string to be printed + * x: horizontal position of the upper left point in pixels + * Special values can be entered: + * CENTER, centers the text + * RIGHT, right justifies the text + * LASTX, continues from last X position; offset can be used: LASTX+n + * y: vertical position of the upper left point in pixels + * Special values can be entered: + * CENTER, centers the text + * BOTTOM, bottom justifies the text + * LASTY, continues from last Y position; offset can be used: LASTY+n + * + */ +//------------------------------------- +void EPD_print(char *st, int x, int y); + +/* + * Set atributes for 7 segment vector font + * == 7 segment font must be the current font to this function to have effect == + * + * Params: + * l: 6~40; distance between bars in pixels + * w: 1~12, max l/2; bar width in pixels + * outline: draw font outline if set to 1 + * color: font outline color, only used if outline=1 + * + */ +//------------------------------------------------------------------------- +void set_7seg_font_atrib(uint8_t l, uint8_t w, int outline, color_t color); + +/* + * Sets the clipping area coordinates. + * All writing to screen is clipped to that area. + * Starting x & y in all functions will be adjusted to the clipping area. + * + * Params: + * x1,y1: upper left point of the clipping area + * x2,y2: bottom right point of the clipping area + * + */ +//---------------------------------------------------------------------- +void EPD_setclipwin(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); + +/* + * Resets the clipping area to full screen (0,0),(_wodth,_height) + * + */ +//---------------------- +void EPD_resetclipwin(); + +/* + * Save current clipping area to temporary variable + * + */ +//--------------------- +void EPD_saveClipWin(); + +/* + * Restore current clipping area from temporary variable + * + */ +//------------------------ +void EPD_restoreClipWin(); + +/* + * returns the string width in pixels. + * Useful for positions strings on the screen. + */ +//-------------------------------- +int EPD_getStringWidth(char* str); + + +/* + * Fills the rectangle occupied by string with current background color + */ +void EPD_clearStringRect(int x, int y, char *str); + + +/* + * Compile font c source file to .fnt file + * which can be used in EPD_setFont() function to select external font + * Created file have the same name as source file and extension .fnt + * + * Params: + * fontfile: pointer to c source font file name; must have .c extension + * dbg: if set to 1, prints debug information + * + * Returns: + * 0 on success + * err no on error + * + */ +//------------------------------------------------ +int compile_font_file(char *fontfile, uint8_t dbg); + +/* + * Get all font's characters to buffer + */ +void getFontCharacters(uint8_t *buf); + +/* + * Decodes and displays JPG image. RGB colors are converted to 4-bit Gray scale + * Limits: + * Baseline only. Progressive and Lossless JPEG format are not supported. + * Image size: Up to 65520 x 65520 pixels + * Color space: YCbCr three components only. Gray scale image is not supported. + * Sampling factor: 4:4:4, 4:2:2 or 4:2:0. + * + * Params: + * x: image left position; constants CENTER & RIGHT can be used; negative value is accepted + * y: image top position; constants CENTER & BOTTOM can be used; negative value is accepted + * scale: image scale factor: 0~3; if scale>0, image is scaled by factor 1/(2^scale) (1/2, 1/4 or 1/8) + * fname: pointer to the name of the file from which the image will be read + * if set to NULL, image will be read from memory buffer pointed to by 'buf' + * buf: pointer to the memory buffer from which the image will be read; used if fname=NULL + * size: size of the memory buffer from which the image will be read; used if fname=NULL & buf!=NULL + * + */ +int EPD_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size); + +#endif diff --git a/components/epaper/EPDspi.c b/components/epaper/EPDspi.c new file mode 100644 index 0000000..278b253 --- /dev/null +++ b/components/epaper/EPDspi.c @@ -0,0 +1,759 @@ +/* + * Author: LoBo (loboris@gmail.com, loboris.github) + * + * Module supporting SPI ePaper displays + * + * HIGH SPEED LOW LEVEL DISPLAY FUNCTIONS + * USING DIRECT or DMA SPI TRANSFER MODEs + * +*/ + + +#include "spi_master_lobo.h" +#include +#include +#include +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_alloc_caps.h" +#include "soc/spi_reg.h" +#include "EPDspi.h" + +#define EPD_DEBUG 1 + +#define EPD2X9 1 + + #define xDot 128 + #define yDot 296 + #define DELAYTIME 1500 + +static uint8_t GDOControl[] = {0x01, (yDot-1)%256, (yDot-1)/256, 0x00}; +static uint8_t softstart[4] = {0x0c, 0xd7, 0xd6, 0x9d}; +static uint8_t VCOMVol[2] = {0x2c, 0xa8}; // VCOM 7c +static uint8_t DummyLine[2] = {0x3a, 0x1a}; // 4 dummy line per gate +static uint8_t Gatetime[2] = {0x3b, 0x08}; // 2us per line +static uint8_t RamDataEntryMode[2] = {0x11, 0x01}; // Ram data entry mode +static uint8_t Border[2] = {0x3c, 0x61}; // Border control ( 0x61: white border; 0x51: black border + +/* +There are totally 20 phases for programmable Source waveform of different phase length. +The phase period defined as TP [n] * T FRAME , where TP [n] range from 0 to 15. +TP [n] = 0 indicates phase skipped +Source Voltage Level: VS [n-XY] is constant in each phase +VS [n-XY] indicates the voltage in phase n for transition from GS X to GS Y + 00 – VSS + 01 – VSH + 10 – VSL + 11 – NA +VS [n-XY] and TP[n] are stored in waveform lookup table register [LUT]. + +VS coding: VS[0-11] VS[0-10] VS[0-01] VS[0-00] + +*/ +// --- VS ---- ---- TP ---- +//uint8_t LUTDefault_full[31] = {0x32, 0x02,0x02,0x01,0x11,0x12,0x12,0x22,0x22,0x66,0x69,0x69,0x59,0x58,0x99,0x99,0x88,0x00,0x00,0x00,0x00, 0xF8,0xB4,0x13,0x51,0x35,0x51,0x51,0x19,0x01,0x00}; +uint8_t LUTDefault_full[31] = {0x32, 0x11,0x11,0x10,0x02,0x02,0x22,0x22,0x22,0x22,0x22,0x51,0x51,0x55,0x88,0x08,0x08,0x88,0x88,0x00,0x00, 0x34,0x23,0x12,0x21,0x24,0x28,0x22,0x21,0xA1,0x01}; +uint8_t LUTDefault_part[31] = {0x32, 0x10,0x18,0x18,0x08,0x18,0x18,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x13,0x14,0x44,0x12,0x00,0x00,0x00,0x00,0x00,0x00}; +uint8_t LUT_gs[31] = {0x32, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; +uint8_t LUTFastest[31] = {0x32, 0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + +uint8_t lvl_buf[16] = {32,70,110,150,185,210,220,225,230,235,240,243,248,251,253,255}; +uint8_t lvl_buf_jpg[16] = {4,8,12,16,22,30,40,60,80,110,140,180,220,240,250,255}; + +uint8_t *LUT_part = LUTDefault_part; +spi_lobo_device_handle_t disp_spi = NULL; +uint8_t *gs_disp_buffer = NULL; +uint8_t *disp_buffer = NULL; +uint8_t *drawBuff = NULL; +uint8_t *gs_drawBuff = NULL; +int _width = EPD_DISPLAY_WIDTH; +int _height = EPD_DISPLAY_HEIGHT; +uint8_t _gs = 0; + +uint16_t gs_used_shades = 0; + +//----------------------------------------------------------- +static void IRAM_ATTR _dma_send(uint8_t *data, uint32_t size) +{ + //Fill DMA descriptors + spi_lobo_dmaworkaround_transfer_active(disp_spi->host->dma_chan); //mark channel as active + spi_lobo_setup_dma_desc_links(disp_spi->host->dmadesc_tx, size, data, false); + disp_spi->host->hw->user.usr_mosi_highpart=0; + disp_spi->host->hw->dma_out_link.addr=(int)(&disp_spi->host->dmadesc_tx[0]) & 0xFFFFF; + disp_spi->host->hw->dma_out_link.start=1; + disp_spi->host->hw->user.usr_mosi_highpart=0; + + disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = (size * 8) - 1; + + // Start transfer + disp_spi->host->hw->cmd.usr = 1; + // Wait for SPI bus ready + while (disp_spi->host->hw->cmd.usr); + + //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. + if (disp_spi->host->dma_chan) spi_lobo_dmaworkaround_idle(disp_spi->host->dma_chan); + + // Reset DMA + disp_spi->host->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; + disp_spi->host->hw->dma_out_link.start=0; + disp_spi->host->hw->dma_in_link.start=0; + disp_spi->host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); + disp_spi->host->hw->dma_conf.out_data_burst_en=1; +} + +//-------------------------------------------------------------------------- +static void IRAM_ATTR _direct_send(uint8_t *data, uint32_t len, uint8_t rep) +{ + uint32_t cidx = 0; // buffer index + uint32_t wd = 0; + int idx = 0; + int bits = 0; + int wbits = 0; + + taskDISABLE_INTERRUPTS(); + + while (len) { + + wd |= (uint32_t)data[idx] << wbits; + wbits += 8; + if (wbits == 32) { + bits += wbits; + wbits = 0; + disp_spi->host->hw->data_buf[idx++] = wd; + wd = 0; + } + len--; // Decrement data counter + if (rep == 0) cidx++; // if not repeating data, increment buffer index + } + if (bits) { + while (disp_spi->host->hw->cmd.usr); // Wait for SPI bus ready + // Load send buffer + disp_spi->host->hw->user.usr_mosi_highpart = 0; + disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = bits-1; + disp_spi->host->hw->user.usr_mosi = 1; + disp_spi->host->hw->miso_dlen.usr_miso_dbitlen = 0; + disp_spi->host->hw->user.usr_miso = 0; + disp_spi->host->hw->cmd.usr = 1; // Start transfer + } + // Wait for SPI bus ready + while (disp_spi->host->hw->cmd.usr); + taskENABLE_INTERRUPTS(); +} + +// ================================================================ +// === Main function to send data to display ====================== +// If rep==true: repeat sending data to display 'len' times +// If rep==false: send 'len' data bytes from buffer to display +// ** Device must already be selected and address window set ** +// ================================================================ +//--------------------------------------------------------------------------- +static void IRAM_ATTR SPI_send_data(uint8_t *data, uint32_t len, uint8_t rep) +{ + if (len == 0) return; + + if ((len*8) <= 512) _direct_send(data, len, rep); + else if (rep == 0) _dma_send(data, len); + else { + // ==== Repeat data, more than 512 bits total ==== + uint8_t *transbuf = pvPortMallocCaps(len, MALLOC_CAP_DMA); + if (transbuf == NULL) return; + + memset(transbuf, data[0], len); + _dma_send(transbuf, len); + free(transbuf); + } +} + +// Send one byte to display +//------------------------------------- +void IRAM_ATTR SPI_Write(uint8_t value) +{ + disp_spi->host->hw->data_buf[0] = (uint32_t)value; + // Load send buffer + disp_spi->host->hw->user.usr_mosi_highpart = 0; + disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7; + disp_spi->host->hw->user.usr_mosi = 1; + disp_spi->host->hw->miso_dlen.usr_miso_dbitlen = 0; + disp_spi->host->hw->user.usr_miso = 0; + // Start transfer + disp_spi->host->hw->cmd.usr = 1; + // Wait for SPI bus ready + while (disp_spi->host->hw->cmd.usr); +} + +// Check display busy line and wait while busy +//----------------------- +static uint8_t ReadBusy() +{ + for (int i=0; i<400; i++){ + if (isEPD_BUSY == EPD_BUSY_LEVEL) return 1; + vTaskDelay(10 / portTICK_RATE_MS); + } + return 0; +} + +//----------------------- +static uint8_t WaitBusy() +{ + if (isEPD_BUSY != EPD_BUSY_LEVEL) return 1; + vTaskDelay(10 / portTICK_RATE_MS); + if (isEPD_BUSY != EPD_BUSY_LEVEL) return 1; + return 0; +} + +// Write one command without parameters +//--------------------------------------- +static void EPD_WriteCMD(uint8_t command) +{ + spi_lobo_device_select(disp_spi, 0); + EPD_DC_0; // command write + SPI_Write(command); +} + +// Write command with one paramet +//--------------------------------------- +static void EPD_WriteCMD_p1(uint8_t command,uint8_t para) +{ + spi_lobo_device_select(disp_spi, 0); + //ReadBusy(); + EPD_DC_0; // command write + SPI_Write(command); + EPD_DC_1; // data write + SPI_Write(para); + spi_lobo_device_deselect(disp_spi); +} + +//---------------- +void EPD_PowerOn() +{ + EPD_WriteCMD_p1(0x22,0xc0); + EPD_WriteCMD(0x20); + //EPD_WriteCMD(0xff); + spi_lobo_device_deselect(disp_spi); +#if EPD_DEBUG + if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n"); + if (!ReadBusy()) printf("[EPD] NOT READY\r\n"); +#else + WaitBusy(); + ReadBusy(); +#endif +} + +//----------------- +void EPD_PowerOff() +{ + EPD_WriteCMD_p1(0x22,0x03); + EPD_WriteCMD(0x20); + //EPD_WriteCMD(0xff); + spi_lobo_device_deselect(disp_spi); +#if EPD_DEBUG + if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n"); + if (!ReadBusy()) printf("[EPD] NOT READY\r\n"); +#else + WaitBusy(); + ReadBusy(); +#endif +#if POWER_Pin + gpio_set_level(DC_Pin, 0); + gpio_set_level(MOSI_Pin, 0); + gpio_set_level(SCK_Pin, 0); + gpio_set_level(RST_Pin, 0); + gpio_set_level(CS_Pin, 0); + gpio_set_level(POWER_Pin, 0); +#endif +} + +// Send command with multiple parameters +//---------------------------------------------------- +static void EPD_Write(uint8_t *value, uint8_t datalen) +{ + uint8_t i = 0; + uint8_t *ptemp; + + ptemp = value; + spi_lobo_device_select(disp_spi, 0); + //ReadBusy(); + EPD_DC_0; // When DC is 0, write command + SPI_Write(*ptemp); //The first byte is written with the command value + ptemp++; + EPD_DC_1; // When DC is 1, write data + for(i= 0;i> 8; + RamAreaY[3] = Yend & 0xFF; + RamAreaY[4] = Yend >> 8; + EPD_Write(RamAreaX, sizeof(RamAreaX)); + EPD_Write(RamAreaY, sizeof(RamAreaY)); +} + +//Set RAM X and Y address counter +/* + === Set RAM Address Counter (4Eh-4Fh) === + adrX[4:0]: Make initial settings for the RAM X address in the address counter (AC). + adrY[8:0]: Make initial settings for the RAM Y address in the address counter (AC). + After RAM data is written, the address counter is automatically updated according to the settings with AM, ID + bits and setting for a new RAM address is not required in the address counter. + Therefore, data is written consecutively without setting an address. + The address counter is not automatically updated when data is read out from the RAM. + RAM address setting cannot be made during the standby mode. + The address setting should be made within the area designated with window addresses which is controlled + by the Data Entry Setting (R11h) {AM, ID[1:0]} ; RAM Address XStart / XEnd Position (R44h) and RAM Address Ystart /Yend Position (R45h). + Otherwise undesirable image will be displayed on the Panel. +*/ +//---------------------------------------------------------- +static void EPD_SetRamPointer(uint8_t addrX, uint16_t addrY) +{ + uint8_t RamPointerX[2]; // default (0,0) + uint8_t RamPointerY[3]; + //Set RAM X address counter + RamPointerX[0] = 0x4e; + RamPointerX[1] = addrX; + //Set RAM Y address counter + RamPointerY[0] = 0x4f; + RamPointerY[1] = addrY & 0xFF; + RamPointerY[2] = addrY >> 8; + + EPD_Write(RamPointerX, sizeof(RamPointerX)); + EPD_Write(RamPointerY, sizeof(RamPointerY)); +} + + +//Set RAM X and Y address Start / End position +//Set RAM X and Y address counter +//---------------------------------------------------------------------------------------------- +static void part_display(uint8_t RAM_XST, uint8_t RAM_XEND ,uint16_t RAM_YST, uint16_t RAM_YEND) +{ + EPD_SetRamArea(RAM_XST, RAM_XEND, RAM_YST, RAM_YEND); + EPD_SetRamPointer (RAM_XST, RAM_YST); +} + +//Initialize the display +//-------------------- +static void EPD_Init() +{ +#if POWER_Pin + gpio_set_level(POWER_Pin, 1); + vTaskDelay(100 / portTICK_RATE_MS); +#else + vTaskDelay(10 / portTICK_RATE_MS); +#endif + // reset + EPD_RST_0; + vTaskDelay(10 / portTICK_RATE_MS); +#if EPD_DEBUG + uint32_t t1 = clock(); +#endif + EPD_RST_1; + for (int n=0; n<50; n++) { + vTaskDelay(10 / portTICK_RATE_MS); + if (isEPD_BUSY == EPD_BUSY_LEVEL) break; + } + + SPI_Write(0x12); // software reset + vTaskDelay(10 / portTICK_RATE_MS); + ReadBusy(); + + // set registers + EPD_Write(GDOControl, sizeof(GDOControl)); // Panel configuration, Gate selection + EPD_Write(softstart, sizeof(softstart)); // X decrease, Y decrease + EPD_Write(VCOMVol, sizeof(VCOMVol)); // VCOM setting + EPD_Write(DummyLine, sizeof(DummyLine)); // dummy line per gate + EPD_Write(Gatetime, sizeof(Gatetime)); // Gate time setting + EPD_Write(Border, sizeof(Border)); + EPD_Write(RamDataEntryMode, sizeof(RamDataEntryMode)); // X increase, Y decrease + + EPD_SetRamArea(0x00, (xDot-1)/8, yDot-1, 0); + EPD_SetRamPointer(0x00, yDot-1); +#if EPD_DEBUG + t1 = clock() - t1; + printf("[EPD] Init: %u ms\r\n", t1); +#endif +} + +//------------------------------ +static void EPD_UpdateFull(void) +{ + /* + + Enable Clock Signal, + + Then Enable CP + - Then Load Temperature value + - Then Load LUT + - Then INITIAL DISPLAY + + Then PATTERN DISPLAY + + Then Disable CP + + Then Disable OSC + */ + EPD_WriteCMD_p1(0x22,0xC7); + EPD_WriteCMD(0x20); + //EPD_WriteCMD(0xff); + spi_lobo_device_deselect(disp_spi); + +#if EPD_DEBUG + if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n"); + if (!ReadBusy()) printf("[EPD] NOT READY\r\n"); +#else + WaitBusy(); + ReadBusy(); +#endif +} + +//------------------------------- +static void EPD_Update_Part(void) +{ + /* + - Enable Clock Signal, + - Then Enable CP + - Then Load Temperature value + - Then Load LUT + - Then INITIAL DISPLAY + + Then PATTERN DISPLAY + - Then Disable CP + - Then Disable OSC + */ + EPD_WriteCMD_p1(0x22,0x04); + EPD_WriteCMD(0x20); + //EPD_WriteCMD(0xff); + spi_lobo_device_deselect(disp_spi); + +#if EPD_DEBUG + if (!WaitBusy()) printf("[EPD] NOT BUSY\r\n"); + if (!ReadBusy()) printf("[EPD] NOT READY\r\n"); +#else + WaitBusy(); + ReadBusy(); +#endif +} + +/******************************************************************************* +Full screen initialization +********************************************************************************/ +static void EPD_init_Full(void) +{ + EPD_Init(); // Reset and set register + EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full)); + + EPD_PowerOn(); +} + +/******************************************************************************* +Part screen initialization +********************************************************************************/ +static void EPD_init_Part(void) +{ + EPD_Init(); // display + EPD_Write((uint8_t *)LUT_part, 31); + EPD_PowerOn(); +} +/******************************************************************************** +parameter: + Label : + =1 Displays the contents of the DisBuffer + =0 Displays the contents of the first byte in DisBuffer, +********************************************************************************/ +static void EPD_Dis_Full(uint8_t *DisBuffer,uint8_t type) +{ + EPD_SetRamPointer(0x00, yDot-1); // set ram pointer + if (type == 0){ + // Fill screen with white + EPD_WriteDispRamMono(xDot, yDot, 0xff); + } + else { + // Fill screen from buffer + EPD_WriteDispRam(xDot, yDot, (uint8_t *)DisBuffer); + } + EPD_UpdateFull(); + +} + +/******************************************************************************** +WARNING: X is smaller screen dimension (0~127) ! + Y is larger screen dimension (0~295) ! +parameter: + xStart : X direction Start coordinate + xEnd : X direction end coordinate + yStart : Y direction Start coordinate + yEnd : Y direction end coordinate + DisBuffer : Display content + type : + =1 Displays the contents of the DisBuffer + =0 Displays the contents of the first byte in DisBuffer, +********************************************************************************/ +static void EPD_Dis_Part(uint8_t xStart, uint8_t xEnd, uint16_t yStart, uint16_t yEnd, uint8_t *DisBuffer, uint8_t type) +{ + if (type == 0) { + // Repeated color + part_display(xStart/8, xEnd/8, yEnd, yStart); + EPD_WriteDispRamMono(xEnd-xStart+1, yEnd-yStart+1, DisBuffer[0]); + EPD_Update_Part(); + part_display(xStart/8, xEnd/8, yEnd, yStart); + EPD_WriteDispRamMono(xEnd-xStart+1, yEnd-yStart+1, DisBuffer[0]); + } + else { + // From buffer + part_display(xStart/8, xEnd/8, yEnd, yStart); + EPD_WriteDispRam(xEnd-xStart+1, yEnd-yStart+1,DisBuffer); + EPD_Update_Part(); + part_display(xStart/8, xEnd/8, yEnd, yStart); + EPD_WriteDispRam(xEnd-xStart+1, yEnd-yStart+1,DisBuffer); + } +} + +//====================================================================================================================================== + +// Clear full screen +//========================= +void EPD_DisplayClearFull() +{ + uint8_t m; + EPD_init_Full(); + +#if EPD_DEBUG + uint32_t t1 = clock(); +#endif + m = 0x00; + EPD_Dis_Full(&m, 0); //all black +#if EPD_DEBUG + t1 = clock() - t1; + printf("[EPD] Clear black: %u ms\r\n", t1); + t1 = clock(); +#endif + m = 0xff; + EPD_Dis_Full(&m, 0); //all white +#if EPD_DEBUG + t1 = clock() - t1; + printf("[EPD] Clear white: %u ms\r\n", t1); +#endif +} + +// Partial clear screen +//========================= +void EPD_DisplayClearPart() +{ + uint8_t m = 0xFF; + EPD_init_Part(); +#if EPD_DEBUG + uint32_t t1 = clock(); + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white + m = 0x00; + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all black + m = 0xFF; + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white + t1 = clock() - t1; + printf("[EPD] Part Clear: %u ms\r\n", t1); +#else + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white + m = 0x00; + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all black + m = 0xFF; + EPD_Dis_Part(0, xDot-1, 0, yDot-1, &m, 0); //all white +#endif +} + +//================================== +void EPD_DisplaySetFull(uint8_t val) +{ + EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full)); +#if EPD_DEBUG + uint32_t t1 = clock(); + EPD_Dis_Full(&val, 0); + t1 = clock() - t1; + printf("[EPD] Display Set Full: %u ms [%02x]\r\n", t1, val); +#else + EPD_Dis_Full(&val, 0); +#endif +} + +//====================================================================================== +void EPD_DisplaySetPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t val) +{ + EPD_Write((uint8_t *)LUT_part, 31); +#if EPD_DEBUG + uint32_t t1 = clock(); + EPD_Dis_Part(yStart,yEnd,xStart,xEnd, &val,0); + t1 = clock() - t1; + printf("[EPD] Display Set Part: %u ms [%02x]\r\n", t1, val); +#else + EPD_Dis_Part(yStart,yEnd,xStart,xEnd, &val,0); +#endif +} + +//====================================== +void EPD_DisplayFull(uint8_t *DisBuffer) +{ + EPD_Write((uint8_t *)LUTDefault_full,sizeof(LUTDefault_full)); +#if EPD_DEBUG + uint32_t t1 = clock(); + EPD_Dis_Full((uint8_t *)DisBuffer,1); + t1 = clock() - t1; + printf("[EPD] Display Full: %u ms\r\n", t1); +#else + EPD_Dis_Full((uint8_t *)DisBuffer,1); +#endif +} + +//========================================================================================== +void EPD_DisplayPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t *DisBuffer) +{ + EPD_Write((uint8_t *)LUT_part, 31); +#if EPD_DEBUG + uint32_t t1 = clock(); + EPD_Dis_Part(yStart,yEnd,xStart,xEnd,(uint8_t *)DisBuffer,1); + t1 = clock() - t1; + printf("[EPD] Display Part: %u ms [%02x:%02x]\r\n", t1, LUT_gs[1], LUT_gs[21]); +#else + EPD_Dis_Part(yStart,yEnd,xStart,xEnd,(uint8_t *)DisBuffer,1); +#endif +} + +//============ +void EPD_Cls() +{ + EPD_DisplaySetPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, 0xFF); + memset(disp_buffer, 0xFF, _width * (_height/8)); + memset(gs_disp_buffer, 0, _width * _height); + gs_used_shades = 0; +} + +//------------------------------------------------------------------------------- +void EPD_gsUpdate(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t gs) +{ + uint8_t val, buf_val, new_val; + int count=0, changed=0; + int x; + uint8_t y; + for (x=xStart; x<=xEnd; x++) { + for (y=yStart; y<=yEnd; y++) { + val = gs_drawBuff[(y * (xEnd-xStart+1)) + x]; + if (val > 15) val >>= 4; + if (val == gs) { + buf_val = drawBuff[(x * ((yEnd-yStart+1)>>3)) + (y>>3)]; + new_val = buf_val; + if (gs > 0) new_val &= (0x80 >> (y % 8)) ^ 0xFF; + else new_val |= (0x80 >> (y % 8)); + if (new_val != buf_val) { + drawBuff[(x * (_height>>3)) + (y>>3)] = new_val; + changed++; + } + count++; + } + } + } + + if (changed) { + #if EPD_DEBUG + printf("[EPD] GS Update %02x, count=%d changed=%d\r\n", gs, count, changed); + #endif + uint8_t *_lutPart = LUT_part; + memset(LUT_gs+1, 0, 30); + if (gs > 0) { + if (gs > 0) { + LUT_gs[1] = 0x18; + LUT_gs[21] = gs; + } + } + else { + LUT_gs[1] = 0x28; + LUT_gs[2] = 0x00; + LUT_gs[21] = 15; + } + LUT_part = LUT_gs; + EPD_DisplayPart(xStart, xEnd, yStart, yEnd, drawBuff); + + LUT_part = _lutPart; + } +} + +//----------------------------------------------------------------------- +void EPD_Update(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd) +{ + if (_gs == 0) EPD_DisplayPart(xStart, xEnd, yStart, yEnd, drawBuff); + else { + for (int n=0; n<16; n++) { + if (gs_used_shades & (1< +#include "spi_master_lobo.h" + +#define EPD_DISPLAY_WIDTH 296 +#define EPD_DISPLAY_HEIGHT 128 + +#define SCK_Pin 18 +#define MOSI_Pin 23 +//#define MISO_Pin 19 +#define DC_Pin 17//26 +#define BUSY_Pin 4//32 +#define RST_Pin 16//27 +#define CS_Pin 5 +// ePaper display can be powered from GPIO +// if powered directly from Vcc, set this to 0 +#define POWER_Pin 22 + +#define DC_VAL (1 << DC_Pin) + +#define EPD_CS_0 gpio_set_level(CS_Pin, 0) +#define EPD_CS_1 gpio_set_level(CS_Pin, 1) +#define isEPD_CS gpio_get_level(CS_Pin) + +#define EPD_RST_0 gpio_set_level(RST_Pin, 0) +#define EPD_RST_1 gpio_set_level(RST_Pin, 1) +#define isEPD_RST gpio_get_level(RST_Pin) + +#define EPD_DC_0 gpio_set_level(DC_Pin, 0) +#define EPD_DC_1 gpio_set_level(DC_Pin, 1) + +#define isEPD_BUSY gpio_get_level(BUSY_Pin) + +#define EPD_BUSY_LEVEL 0 + +// ================================================== +// Define which spi bus to use VSPI_HOST or HSPI_HOST +#define SPI_BUS VSPI_HOST +// ================================================== + +spi_lobo_device_handle_t disp_spi; +uint8_t *gs_disp_buffer; +uint8_t *disp_buffer; +uint8_t *gs_drawBuff; +uint8_t *drawBuff; +int _width; +int _height; +uint16_t gs_used_shades; +uint8_t _gs; +uint8_t *LUT_part; +uint8_t LUTDefault_fastest[31]; +uint8_t LUTDefault_part[31]; +uint8_t LUT_gs[31]; +uint8_t LUTDefault_full[31]; +uint8_t lvl_buf[16]; +uint8_t lvl_buf_jpg[16]; + +void EPD_wait(uint32_t ms); +void EPD_DisplaySetFull(uint8_t val); +void EPD_DisplaySetPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t val); +void EPD_DisplayClearFull(); +void EPD_DisplayClearPart(); +void EPD_DisplayFull(uint8_t *DisBuffer); +void EPD_DisplayPart(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t *DisBuffer); +void EPD_gsUpdate(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd, uint8_t gs); +void EPD_Update(int xStart, int xEnd, uint8_t yStart, uint8_t yEnd); +void EPD_UpdateScreen(); +void EPD_Cls(); +void EPD_PowerOn(); +void EPD_PowerOff(); + + +#endif diff --git a/components/epaper/SmallFont.c b/components/epaper/SmallFont.c new file mode 100644 index 0000000..663f59c --- /dev/null +++ b/components/epaper/SmallFont.c @@ -0,0 +1,120 @@ +// SmallFont.c +// Font type : Full (95 characters) +// Font size : 8x12 pixels +// Memory usage : 1144 bytes + +#if defined(__AVR__) + #include + #define fontdatatype const uint8_t +#elif defined(__PIC32MX__) + #define PROGMEM + #define fontdatatype const unsigned char +#elif defined(__arm__) + #define PROGMEM + #define fontdatatype const unsigned char +#endif + +const unsigned char tft_SmallFont[1144] = +{ +0x08,0x0C,0x20,0x5F, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // +0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x00,0x00, // ! +0x00,0x28,0x50,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " +0x00,0x00,0x28,0x28,0xFC,0x28,0x50,0xFC,0x50,0x50,0x00,0x00, // # +0x00,0x20,0x78,0xA8,0xA0,0x60,0x30,0x28,0xA8,0xF0,0x20,0x00, // $ +0x00,0x00,0x48,0xA8,0xB0,0x50,0x28,0x34,0x54,0x48,0x00,0x00, // % +0x00,0x00,0x20,0x50,0x50,0x78,0xA8,0xA8,0x90,0x6C,0x00,0x00, // & +0x00,0x40,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' +0x00,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x00, // ( +0x00,0x40,0x20,0x10,0x10,0x10,0x10,0x10,0x10,0x20,0x40,0x00, // ) +0x00,0x00,0x00,0x20,0xA8,0x70,0x70,0xA8,0x20,0x00,0x00,0x00, // * +0x00,0x00,0x20,0x20,0x20,0xF8,0x20,0x20,0x20,0x00,0x00,0x00, // + +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80, // , +0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // - +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00, // . +0x00,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40,0x40,0x80,0x00, // / + +0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // 0 +0x00,0x00,0x20,0x60,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // 1 +0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x40,0x80,0xF8,0x00,0x00, // 2 +0x00,0x00,0x70,0x88,0x08,0x30,0x08,0x08,0x88,0x70,0x00,0x00, // 3 +0x00,0x00,0x10,0x30,0x50,0x50,0x90,0x78,0x10,0x18,0x00,0x00, // 4 +0x00,0x00,0xF8,0x80,0x80,0xF0,0x08,0x08,0x88,0x70,0x00,0x00, // 5 +0x00,0x00,0x70,0x90,0x80,0xF0,0x88,0x88,0x88,0x70,0x00,0x00, // 6 +0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x20,0x20,0x20,0x00,0x00, // 7 +0x00,0x00,0x70,0x88,0x88,0x70,0x88,0x88,0x88,0x70,0x00,0x00, // 8 +0x00,0x00,0x70,0x88,0x88,0x88,0x78,0x08,0x48,0x70,0x00,0x00, // 9 +0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x00,0x00, // : +0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x20,0x00, // ; +0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // < +0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0x00,0x00, // = +0x00,0x40,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x40,0x00,0x00, // > +0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20,0x00,0x00, // ? + +0x00,0x00,0x70,0x88,0x98,0xA8,0xA8,0xB8,0x80,0x78,0x00,0x00, // @ +0x00,0x00,0x20,0x20,0x30,0x50,0x50,0x78,0x48,0xCC,0x00,0x00, // A +0x00,0x00,0xF0,0x48,0x48,0x70,0x48,0x48,0x48,0xF0,0x00,0x00, // B +0x00,0x00,0x78,0x88,0x80,0x80,0x80,0x80,0x88,0x70,0x00,0x00, // C +0x00,0x00,0xF0,0x48,0x48,0x48,0x48,0x48,0x48,0xF0,0x00,0x00, // D +0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x48,0xF8,0x00,0x00, // E +0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x40,0xE0,0x00,0x00, // F +0x00,0x00,0x38,0x48,0x80,0x80,0x9C,0x88,0x48,0x30,0x00,0x00, // G +0x00,0x00,0xCC,0x48,0x48,0x78,0x48,0x48,0x48,0xCC,0x00,0x00, // H +0x00,0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // I +0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x90,0xE0,0x00, // J +0x00,0x00,0xEC,0x48,0x50,0x60,0x50,0x50,0x48,0xEC,0x00,0x00, // K +0x00,0x00,0xE0,0x40,0x40,0x40,0x40,0x40,0x44,0xFC,0x00,0x00, // L +0x00,0x00,0xD8,0xD8,0xD8,0xD8,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // M +0x00,0x00,0xDC,0x48,0x68,0x68,0x58,0x58,0x48,0xE8,0x00,0x00, // N +0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // O + +0x00,0x00,0xF0,0x48,0x48,0x70,0x40,0x40,0x40,0xE0,0x00,0x00, // P +0x00,0x00,0x70,0x88,0x88,0x88,0x88,0xE8,0x98,0x70,0x18,0x00, // Q +0x00,0x00,0xF0,0x48,0x48,0x70,0x50,0x48,0x48,0xEC,0x00,0x00, // R +0x00,0x00,0x78,0x88,0x80,0x60,0x10,0x08,0x88,0xF0,0x00,0x00, // S +0x00,0x00,0xF8,0xA8,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // T +0x00,0x00,0xCC,0x48,0x48,0x48,0x48,0x48,0x48,0x30,0x00,0x00, // U +0x00,0x00,0xCC,0x48,0x48,0x50,0x50,0x30,0x20,0x20,0x00,0x00, // V +0x00,0x00,0xA8,0xA8,0xA8,0x70,0x50,0x50,0x50,0x50,0x00,0x00, // W +0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x50,0x50,0xD8,0x00,0x00, // X +0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // Y +0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x40,0x48,0xF8,0x00,0x00, // Z +0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x38,0x00, // [ +0x00,0x40,0x40,0x40,0x20,0x20,0x10,0x10,0x10,0x08,0x00,0x00, // +0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x70,0x00, // ] +0x00,0x20,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC, // _ + +0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ` +0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x38,0x48,0x3C,0x00,0x00, // a +0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0x70,0x00,0x00, // b +0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x40,0x40,0x38,0x00,0x00, // c +0x00,0x00,0x18,0x08,0x08,0x38,0x48,0x48,0x48,0x3C,0x00,0x00, // d +0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x78,0x40,0x38,0x00,0x00, // e +0x00,0x00,0x1C,0x20,0x20,0x78,0x20,0x20,0x20,0x78,0x00,0x00, // f +0x00,0x00,0x00,0x00,0x00,0x3C,0x48,0x30,0x40,0x78,0x44,0x38, // g +0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0xEC,0x00,0x00, // h +0x00,0x00,0x20,0x00,0x00,0x60,0x20,0x20,0x20,0x70,0x00,0x00, // i +0x00,0x00,0x10,0x00,0x00,0x30,0x10,0x10,0x10,0x10,0x10,0xE0, // j +0x00,0x00,0xC0,0x40,0x40,0x5C,0x50,0x70,0x48,0xEC,0x00,0x00, // k +0x00,0x00,0xE0,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // l +0x00,0x00,0x00,0x00,0x00,0xF0,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // m +0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0xEC,0x00,0x00, // n +0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x48,0x48,0x30,0x00,0x00, // o + +0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0x70,0x40,0xE0, // p +0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x48,0x48,0x38,0x08,0x1C, // q +0x00,0x00,0x00,0x00,0x00,0xD8,0x60,0x40,0x40,0xE0,0x00,0x00, // r +0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x30,0x08,0x78,0x00,0x00, // s +0x00,0x00,0x00,0x20,0x20,0x70,0x20,0x20,0x20,0x18,0x00,0x00, // t +0x00,0x00,0x00,0x00,0x00,0xD8,0x48,0x48,0x48,0x3C,0x00,0x00, // u +0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x00,0x00, // v +0x00,0x00,0x00,0x00,0x00,0xA8,0xA8,0x70,0x50,0x50,0x00,0x00, // w +0x00,0x00,0x00,0x00,0x00,0xD8,0x50,0x20,0x50,0xD8,0x00,0x00, // x +0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x20,0xC0, // y +0x00,0x00,0x00,0x00,0x00,0x78,0x10,0x20,0x20,0x78,0x00,0x00, // z +0x00,0x18,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x10,0x18,0x00, // { +0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, // | +0x00,0x60,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x60,0x00, // } +0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ~ +}; diff --git a/components/epaper/Ubuntu16.c b/components/epaper/Ubuntu16.c new file mode 100644 index 0000000..4f75855 --- /dev/null +++ b/components/epaper/Ubuntu16.c @@ -0,0 +1,331 @@ +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// on Arduinos. It can however handle any ttf font that has been converted +// using the conversion program. These could be fixed width or proportional +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// Ubuntu16.c +// Point Size : 16 +// Memory usage : 1433 bytes +// # characters : 95 + +// Header Format (to make Arduino UTFT Compatible): +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +const unsigned char tft_Ubuntu16[] = +{ +0x00, 0x10, 0x00, 0x00, + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. + +// ' ' +0x20,0x0D,0x00,0x00,0x00,0x04, + +// '!' +0x21,0x02,0x01,0x0B,0x01,0x04, +0xFC,0x60, +// '"' +0x22,0x00,0x04,0x04,0x01,0x07, +0x99,0x99, +// '#' +0x23,0x02,0x09,0x0B,0x01,0x0B, +0x11,0x08,0x84,0x5F,0xF2,0x21,0x10,0x89,0xFF,0x44,0x22,0x11,0x00, +// '$' +0x24,0x00,0x07,0x10,0x01,0x09, +0x10,0x20,0xF6,0x08,0x10,0x18,0x08,0x0C,0x0C,0x08,0x3F,0xC2,0x04,0x00, +// '%' +0x25,0x02,0x0C,0x0B,0x01,0x0E, +0x70,0x4D,0x88,0x89,0x08,0x90,0xDA,0x07,0x4E,0x05,0xB0,0x91,0x09,0x11,0x1B,0x20,0xE0, +// '&' +0x26,0x02,0x0A,0x0B,0x01,0x0B, +0x3C,0x18,0x84,0x21,0x08,0x2C,0x0C,0x04,0x8A,0x10,0x83,0x30,0xC7,0xC8, +// ''' +0x27,0x00,0x01,0x04,0x01,0x04, +0xF0, +// '(' +0x28,0x00,0x04,0x10,0x01,0x05, +0x02,0x44,0x48,0x88,0x88,0x84,0x44,0x20, +// ')' +0x29,0x00,0x04,0x10,0x00,0x05, +0x04,0x22,0x21,0x11,0x11,0x12,0x22,0x40, +// '*' +0x2A,0x02,0x09,0x06,0x00,0x08, +0x08,0x24,0x8F,0x83,0x81,0x41,0x10, +// '+' +0x2B,0x05,0x07,0x07,0x01,0x09, +0x10,0x20,0x47,0xF1,0x02,0x04,0x00, +// ',' +0x2C,0x0B,0x02,0x05,0x00,0x04, +0x54,0x80, +// '-' +0x2D,0x08,0x04,0x01,0x01,0x06, +0xF0, +// '.' +0x2E,0x0B,0x01,0x02,0x01,0x04, +0xC0, +// '/' +0x2F,0x00,0x07,0x10,0x00,0x06, +0x02,0x08,0x10,0x20,0x81,0x02,0x08,0x10,0x40,0x81,0x04,0x08,0x10,0x40, +// '0' +0x30,0x02,0x07,0x0B,0x01,0x09, +0x38,0x8B,0x1C,0x18,0x30,0x60,0xC1,0x86,0x88,0xE0, +// '1' +0x31,0x02,0x04,0x0B,0x01,0x09, +0x13,0x59,0x11,0x11,0x11,0x10, +// '2' +0x32,0x02,0x06,0x0B,0x01,0x09, +0x7A,0x30,0x41,0x08,0x21,0x08,0x42,0x0F,0xC0, +// '3' +0x33,0x02,0x07,0x0B,0x01,0x09, +0x78,0x08,0x08,0x10,0x47,0x01,0x01,0x02,0x0B,0xE0, +// '4' +0x34,0x02,0x07,0x0B,0x01,0x09, +0x04,0x18,0x51,0x22,0x48,0xA1,0x7F,0x04,0x08,0x10, +// '5' +0x35,0x02,0x07,0x0B,0x01,0x09, +0x7E,0x81,0x02,0x07,0x81,0x80,0x81,0x02,0x0B,0xE0, +// '6' +0x36,0x02,0x07,0x0B,0x01,0x09, +0x1C,0x61,0x00,0x0F,0x90,0xA0,0xC1,0x82,0x88,0xE0, +// '7' +0x37,0x02,0x07,0x0B,0x01,0x09, +0xFE,0x04,0x10,0x40,0x82,0x04,0x08,0x20,0x40,0x80, +// '8' +0x38,0x02,0x07,0x0B,0x01,0x09, +0x39,0x8A,0x0C,0x14,0x47,0x11,0x41,0x83,0x89,0xE0, +// '9' +0x39,0x02,0x07,0x0B,0x01,0x09, +0x38,0x8A,0x0C,0x18,0x28,0x4F,0x81,0x04,0x11,0xC0, +// ':' +0x3A,0x05,0x01,0x08,0x01,0x04, +0xC3, +// ';' +0x3B,0x05,0x02,0x0B,0x00,0x04, +0x50,0x05,0x48, +// '<' +0x3C,0x05,0x08,0x07,0x01,0x09, +0x02,0x0C,0x30,0x60,0x30,0x0C,0x02, +// '=' +0x3D,0x06,0x07,0x04,0x01,0x09, +0xFE,0x00,0x07,0xF0, +// '>' +0x3E,0x05,0x09,0x07,0x00,0x09, +0x40,0x1C,0x01,0x80,0x70,0x61,0xC1,0x00, +// '?' +0x3F,0x02,0x06,0x0B,0x01,0x07, +0x78,0x30,0x41,0x18,0xC2,0x00,0x00,0x82,0x00, +// '@' +0x40,0x02,0x0D,0x0D,0x01,0x0F, +0x0F,0x81,0x83,0x10,0x0C,0x8F,0xA8,0x84,0xC8,0x26,0x41,0x32,0x09,0x88,0x5A,0x3F,0x90,0x00,0x60,0x00,0xFC,0x00, +// 'A' +0x41,0x02,0x0B,0x0B,0x00,0x0B, +0x04,0x01,0xC0,0x28,0x08,0x81,0x10,0x61,0x08,0x21,0xFC,0x60,0x48,0x0B,0x00,0x80, +// 'B' +0x42,0x02,0x08,0x0B,0x01,0x0A, +0xF8,0x86,0x82,0x82,0x86,0xFC,0x82,0x81,0x81,0x82,0xFC, +// 'C' +0x43,0x02,0x09,0x0B,0x01,0x0B, +0x1F,0x10,0x10,0x10,0x08,0x04,0x02,0x01,0x00,0x40,0x30,0x07,0xC0, +// 'D' +0x44,0x02,0x09,0x0B,0x01,0x0B, +0xFC,0x41,0x20,0x50,0x18,0x0C,0x06,0x03,0x01,0x81,0x41,0x3F,0x00, +// 'E' +0x45,0x02,0x07,0x0B,0x01,0x09, +0xFF,0x02,0x04,0x08,0x1F,0xA0,0x40,0x81,0x03,0xF8, +// 'F' +0x46,0x02,0x07,0x0B,0x01,0x09, +0xFF,0x02,0x04,0x08,0x1F,0xA0,0x40,0x81,0x02,0x00, +// 'G' +0x47,0x02,0x09,0x0B,0x01,0x0B, +0x1F,0x10,0x10,0x10,0x08,0x04,0x02,0x03,0x01,0x40,0xB0,0x47,0xE0, +// 'H' +0x48,0x02,0x09,0x0B,0x01,0x0B, +0x80,0xC0,0x60,0x30,0x18,0x0F,0xFE,0x03,0x01,0x80,0xC0,0x60,0x20, +// 'I' +0x49,0x02,0x01,0x0B,0x01,0x03, +0xFF,0xE0, +// 'J' +0x4A,0x02,0x07,0x0B,0x00,0x08, +0x02,0x04,0x08,0x10,0x20,0x40,0x81,0x02,0x09,0xE0, +// 'K' +0x4B,0x02,0x09,0x0B,0x01,0x0A, +0x81,0x41,0x23,0x12,0x0A,0x06,0x02,0xC1,0x10,0x86,0x40,0xA0,0x20, +// 'L' +0x4C,0x02,0x07,0x0B,0x01,0x08, +0x81,0x02,0x04,0x08,0x10,0x20,0x40,0x81,0x03,0xF8, +// 'M' +0x4D,0x02,0x0B,0x0B,0x01,0x0D, +0x40,0x4C,0x19,0x01,0x28,0xA5,0x14,0x94,0xB2,0x9C,0x33,0x84,0x30,0x06,0x00,0x80, +// 'N' +0x4E,0x02,0x09,0x0B,0x01,0x0B, +0x80,0xE0,0x68,0x32,0x19,0x0C,0x46,0x13,0x05,0x82,0xC0,0xE0,0x20, +// 'O' +0x4F,0x02,0x0B,0x0B,0x01,0x0D, +0x1F,0x04,0x11,0x01,0x40,0x18,0x03,0x00,0x60,0x0C,0x01,0x40,0x44,0x10,0x7C,0x00, +// 'P' +0x50,0x02,0x08,0x0B,0x01,0x0A, +0xFC,0x82,0x81,0x81,0x81,0x82,0xFC,0x80,0x80,0x80,0x80, +// 'Q' +0x51,0x02,0x0B,0x0E,0x01,0x0D, +0x1F,0x04,0x11,0x01,0x40,0x18,0x03,0x00,0x60,0x0C,0x01,0x40,0x44,0x10,0x78,0x02,0x00,0x30,0x01,0x80, +// 'R' +0x52,0x02,0x09,0x0B,0x01,0x0A, +0xFC,0x41,0x20,0x50,0x28,0x14,0x13,0xF1,0x08,0x82,0x40,0xA0,0x20, +// 'S' +0x53,0x02,0x08,0x0B,0x01,0x09, +0x3C,0xC2,0x80,0x80,0x40,0x1C,0x06,0x02,0x02,0x06,0x78, +// 'T' +0x54,0x02,0x09,0x0B,0x00,0x09, +0xFF,0x84,0x02,0x01,0x00,0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x00, +// 'U' +0x55,0x02,0x09,0x0B,0x01,0x0B, +0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xA0,0x8F,0x80, +// 'V' +0x56,0x02,0x09,0x0B,0x00,0x09, +0x80,0xE0,0xD0,0x48,0x26,0x21,0x10,0x88,0x28,0x14,0x0E,0x02,0x00, +// 'W' +0x57,0x02,0x0D,0x0B,0x00,0x0D, +0x80,0x0E,0x10,0xD0,0x84,0x8E,0x24,0x51,0x22,0x88,0xA2,0x85,0x14,0x38,0xE0,0xC2,0x04,0x10, +// 'X' +0x58,0x02,0x09,0x0B,0x00,0x09, +0xC1,0xA0,0x88,0x86,0xC1,0x40,0x60,0x70,0x6C,0x22,0x20,0xB0,0x60, +// 'Y' +0x59,0x02,0x09,0x0B,0x00,0x09, +0x80,0xA0,0x90,0x44,0x41,0x40,0xA0,0x20,0x10,0x08,0x04,0x02,0x00, +// 'Z' +0x5A,0x02,0x07,0x0B,0x01,0x09, +0xFE,0x04,0x10,0x41,0x02,0x08,0x00,0x41,0x03,0xF8, +// '[' +0x5B,0x00,0x03,0x10,0x02,0x05, +0xF2,0x49,0x24,0x92,0x49,0x27, +// '\' +0x5C,0x00,0x07,0x10,0x00,0x06, +0x80,0x81,0x02,0x02,0x04,0x08,0x08,0x10,0x10,0x20,0x40,0x40,0x81,0x01, +// ']' +0x5D,0x00,0x03,0x10,0x00,0x05, +0xE4,0x92,0x49,0x24,0x92,0x4F, +// '^' +0x5E,0x02,0x07,0x06,0x01,0x09, +0x10,0x70,0xA2,0x24,0x50,0x40, +// '_' +0x5F,0x0F,0x08,0x01,0x00,0x08, +0xFF, +// '`' +0x60,0x01,0x04,0x03,0x01,0x06, +0x86,0x10, +// 'a' +0x61,0x05,0x06,0x08,0x01,0x08, +0x78,0x30,0x5F,0xC6,0x18,0x5F, +// 'b' +0x62,0x01,0x07,0x0C,0x01,0x09, +0x81,0x02,0x04,0x0F,0x90,0xA0,0xC1,0x83,0x06,0x17,0xC0, +// 'c' +0x63,0x05,0x06,0x08,0x01,0x08, +0x3D,0x08,0x20,0x82,0x04,0x0F, +// 'd' +0x64,0x01,0x07,0x0C,0x01,0x09, +0x02,0x04,0x08,0x13,0xE8,0x60,0xC1,0x83,0x05,0x09,0xF0, +// 'e' +0x65,0x05,0x07,0x08,0x01,0x09, +0x3C,0x8A,0x0F,0xF8,0x10,0x10,0x1E, +// 'f' +0x66,0x01,0x05,0x0C,0x01,0x06, +0x7E,0x21,0x0F,0xC2,0x10,0x84,0x21,0x00, +// 'g' +0x67,0x05,0x07,0x0B,0x01,0x09, +0x3E,0x86,0x0C,0x18,0x30,0x50,0x9F,0x02,0x0B,0xE0, +// 'h' +0x68,0x01,0x07,0x0C,0x01,0x09, +0x81,0x02,0x04,0x0F,0x90,0xE0,0xC1,0x83,0x06,0x0C,0x10, +// 'i' +0x69,0x01,0x03,0x0C,0x00,0x03, +0x48,0x04,0x92,0x49,0x20, +// 'j' +0x6A,0x01,0x04,0x0F,0xFF,0x03, +0x22,0x00,0x22,0x22,0x22,0x22,0x22,0xC0, +// 'k' +0x6B,0x01,0x06,0x0C,0x01,0x08, +0x82,0x08,0x20,0x8A,0x4A,0x30,0xA2,0x48,0xA1, +// 'l' +0x6C,0x01,0x04,0x0C,0x01,0x04, +0x88,0x88,0x88,0x88,0x88,0x86, +// 'm' +0x6D,0x05,0x0B,0x08,0x01,0x0D, +0xFB,0xD1,0x8E,0x10,0xC2,0x18,0x43,0x08,0x61,0x0C,0x21, +// 'n' +0x6E,0x05,0x07,0x08,0x01,0x09, +0xFD,0x0E,0x0C,0x18,0x30,0x60,0xC1, +// 'o' +0x6F,0x05,0x08,0x08,0x01,0x0A, +0x3C,0x42,0x81,0x81,0x81,0x81,0x42,0x3C, +// 'p' +0x70,0x05,0x07,0x0B,0x01,0x09, +0xF9,0x0A,0x0C,0x18,0x30,0x61,0x7C,0x81,0x02,0x00, +// 'q' +0x71,0x05,0x07,0x0B,0x01,0x09, +0x3E,0x86,0x0C,0x18,0x30,0x50,0x9F,0x02,0x04,0x08, +// 'r' +0x72,0x05,0x05,0x08,0x01,0x06, +0xFC,0x21,0x08,0x42,0x10, +// 's' +0x73,0x05,0x05,0x08,0x01,0x07, +0x7C,0x20,0xC3,0x04,0x3E, +// 't' +0x74,0x02,0x05,0x0B,0x01,0x07, +0x84,0x21,0xF8,0x42,0x10,0x84,0x1E, +// 'u' +0x75,0x05,0x07,0x08,0x01,0x09, +0x83,0x06,0x0C,0x18,0x30,0x50,0xBF, +// 'v' +0x76,0x05,0x07,0x08,0x00,0x07, +0x83,0x05,0x12,0x22,0x85,0x0E,0x08, +// 'w' +0x77,0x05,0x0D,0x08,0x00,0x0D, +0x82,0x0C,0x10,0x51,0xC4,0x8A,0x26,0x5B,0x14,0x50,0xE3,0x82,0x08, +// 'x' +0x78,0x05,0x08,0x08,0x00,0x08, +0xC3,0x66,0x24,0x18,0x18,0x24,0x42,0xC3, +// 'y' +0x79,0x05,0x07,0x0B,0x00,0x07, +0x82,0x89,0x12,0x22,0x85,0x04,0x08,0x10,0x43,0x00, +// 'z' +0x7A,0x05,0x06,0x08,0x01,0x08, +0xFC,0x10,0x84,0x21,0x08,0x3F, +// '{' +0x7B,0x00,0x05,0x10,0x00,0x05, +0x19,0x08,0x42,0x10,0x98,0x61,0x08,0x42,0x10,0x83, +// '|' +0x7C,0x00,0x01,0x10,0x02,0x05, +0xFF,0xFF, +// '}' +0x7D,0x00,0x05,0x10,0x00,0x05, +0xC1,0x08,0x42,0x10,0x83,0x31,0x08,0x42,0x10,0x98, +// '~' +0x7E,0x07,0x07,0x02,0x01,0x09, +0x73,0x18, + +// Terminator +0xFF +}; diff --git a/components/epaper/comic24.c b/components/epaper/comic24.c new file mode 100644 index 0000000..2e534c9 --- /dev/null +++ b/components/epaper/comic24.c @@ -0,0 +1,331 @@ +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// on Arduinos. It can however handle any ttf font that has been converted +// using the conversion program. These could be fixed width or proportional +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// comic.c +// Point Size : 24 +// Memory usage : 2814 bytes +// # characters : 95 + +// Header Format (to make Arduino UTFT Compatible): +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +unsigned char tft_Comic24[] = +{ +0x00, 0x19, 0x00, 0x00, + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. + +// ' ' +0x20,0x15,0x00,0x00,0x00,0x07, + +// '!' +0x21,0x02,0x02,0x14,0x01,0x06, +0xFF,0xFF,0xFF,0xFC,0x2D, +// '"' +0x22,0x03,0x06,0x08,0x02,0x0A, +0xCF,0x3C,0xF3,0xCF,0x3C,0xF3, +// '#' +0x23,0x03,0x14,0x12,0x01,0x14, +0x01,0x81,0x80,0x18,0x18,0x01,0x81,0x80,0x30,0x30,0x03,0x03,0x07,0xFF,0xFF,0x7F,0xFF,0xF0,0x60,0x60,0x06,0x06,0x00,0xC0,0xC0,0x0C,0x0C,0x0F,0xFF,0xFE,0xFF,0xFF,0xE1,0x81,0x80,0x18,0x18,0x03,0x83,0x00,0x30,0x30,0x03,0x03,0x00, +// '$' +0x24,0x00,0x0B,0x19,0x02,0x11, +0x0C,0x01,0x80,0x30,0x0F,0x83,0xFC,0xD9,0xBB,0x06,0x60,0xCC,0x19,0x83,0xB0,0x3F,0x83,0xFC,0x1B,0x83,0x18,0x63,0x0C,0x71,0x9F,0x37,0x7F,0xC3,0xF0,0x18,0x03,0x00,0x60,0x0C,0x00, +// '%' +0x25,0x01,0x11,0x14,0x02,0x14, +0x00,0x00,0x00,0x0C,0x0E,0x0E,0x0F,0x86,0x0C,0x67,0x06,0x33,0x03,0x19,0x80,0xF9,0x80,0x38,0xC0,0x00,0xE0,0x00,0x60,0x00,0x70,0x00,0x31,0xE0,0x39,0xF8,0x19,0xCE,0x1C,0xC3,0x0C,0x61,0x86,0x39,0xC6,0x0F,0xC3,0x03,0xC0, +// '&' +0x26,0x03,0x0F,0x13,0x01,0x10, +0x01,0xC0,0x07,0xC0,0x19,0x80,0x33,0x00,0x6E,0x00,0xF8,0x01,0xE0,0x07,0x80,0x1F,0x8C,0x73,0x19,0xC3,0x37,0x07,0xEC,0x07,0xD8,0x07,0x30,0x0E,0x38,0x7E,0x3F,0xEC,0x3F,0x0C,0x00,0x18, +// ''' +0x27,0x03,0x02,0x06,0x03,0x09, +0xFF,0xF0, +// '(' +0x28,0x02,0x07,0x18,0x01,0x09, +0x06,0x1C,0x71,0xC3,0x0E,0x18,0x30,0xE1,0x83,0x06,0x0C,0x18,0x30,0x60,0xE0,0xC1,0x83,0x83,0x83,0x87,0x83, +// ')' +0x29,0x02,0x06,0x18,0x02,0x09, +0xC3,0x86,0x0C,0x30,0x61,0x86,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x61,0x86,0x31,0xCE,0x30, +// '*' +0x2A,0x03,0x0B,0x09,0x01,0x0D, +0x0C,0x01,0x83,0xBF,0xFF,0xF3,0xFC,0x3C,0x0F,0xC3,0x9C,0x61,0x80, +// '+' +0x2B,0x09,0x0A,0x0A,0x00,0x0C, +0x0C,0x03,0x00,0xC0,0x30,0xFF,0xFF,0xF0,0xC0,0x30,0x0C,0x03,0x00, +// ',' +0x2C,0x13,0x04,0x06,0x02,0x07, +0x37,0x66,0xEC, +// '-' +0x2D,0x0E,0x08,0x02,0x01,0x0A, +0xFF,0xFF, +// '.' +0x2E,0x12,0x03,0x03,0x02,0x06, +0xFF,0x80, +// '/' +0x2F,0x01,0x0A,0x15,0x01,0x0C, +0x00,0x00,0x30,0x0C,0x06,0x01,0x80,0x60,0x30,0x0C,0x06,0x01,0x80,0xC0,0x30,0x18,0x06,0x03,0x00,0xC0,0x60,0x18,0x0E,0x03,0x00,0xC0,0x00, +// '0' +0x30,0x03,0x0D,0x12,0x01,0x0F, +0x0F,0x80,0xFF,0x0E,0x18,0xE0,0x66,0x03,0x70,0x0F,0x00,0x78,0x03,0xC0,0x1E,0x00,0xF0,0x07,0x80,0x3C,0x03,0xB0,0x19,0x81,0xC7,0x1C,0x3F,0xC0,0x7C,0x00, +// '1' +0x31,0x03,0x06,0x12,0x03,0x0B, +0x10,0xC7,0x3C,0xB0,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0xFF,0xF0, +// '2' +0x32,0x03,0x0B,0x12,0x02,0x0F, +0x1F,0x07,0xFB,0xC3,0xE0,0x30,0x06,0x00,0xC0,0x38,0x0E,0x07,0x81,0xE0,0xF8,0x3C,0x07,0x01,0xC0,0x30,0x06,0x00,0xFF,0xDF,0xFC, +// '3' +0x33,0x03,0x0B,0x12,0x02,0x0F, +0x1F,0x0F,0xF9,0xC3,0x80,0x30,0x06,0x00,0xC0,0x78,0x7E,0x0F,0x80,0x78,0x03,0x80,0x30,0x06,0x00,0xF0,0x1F,0x0E,0x7F,0x83,0xE0, +// '4' +0x34,0x03,0x0D,0x12,0x02,0x0F, +0x01,0xC0,0x0E,0x00,0xF0,0x0F,0x80,0x6C,0x07,0x60,0x33,0x03,0x98,0x38,0xC1,0x86,0x1C,0x31,0xFF,0xFF,0xFF,0x80,0x60,0x03,0x00,0x18,0x00,0xC0,0x06,0x00, +// '5' +0x35,0x02,0x0C,0x13,0x02,0x0F, +0x00,0x0F,0xFE,0xFF,0xE6,0x00,0x60,0x0E,0x00,0xEF,0x8F,0xFC,0xF8,0x6E,0x07,0xC0,0x30,0x03,0x00,0x30,0x03,0x00,0x7C,0x06,0xE1,0xE7,0xFC,0x3F,0x00, +// '6' +0x36,0x03,0x0C,0x12,0x01,0x0F, +0x03,0x00,0x70,0x0E,0x01,0xC0,0x38,0x03,0x00,0x60,0x06,0xF8,0xFF,0xEE,0x0E,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x77,0x0E,0x3F,0xC1,0xF8, +// '7' +0x37,0x02,0x0D,0x13,0x01,0x0F, +0x00,0x07,0xFF,0xFF,0xFE,0x00,0xE0,0x0E,0x00,0x60,0x06,0x00,0x30,0x03,0x80,0x18,0x01,0xC0,0x0C,0x00,0x60,0x07,0x00,0x30,0x03,0x80,0x18,0x00,0xC0,0x04,0x00, +// '8' +0x38,0x02,0x0C,0x13,0x01,0x0F, +0x00,0x00,0xFC,0x3F,0xE3,0x07,0x60,0x36,0x03,0x60,0x37,0x8F,0x3F,0xE1,0xFE,0x38,0xE7,0x07,0x60,0x36,0x03,0x60,0x36,0x03,0x30,0x63,0xFE,0x0F,0x80, +// '9' +0x39,0x03,0x0D,0x13,0x01,0x0F, +0x0F,0x01,0xFE,0x1C,0x38,0xC0,0xCC,0x07,0x60,0x1B,0x00,0xD8,0x06,0xE0,0x73,0x87,0x8F,0xF8,0x3E,0xC0,0x0E,0x00,0x60,0x07,0x00,0xF0,0x1F,0x03,0xE0,0x1C,0x00, +// ':' +0x3A,0x09,0x03,0x0B,0x02,0x07, +0xFF,0x80,0x00,0xFF,0x80, +// ';' +0x3B,0x09,0x04,0x0E,0x02,0x07, +0xEE,0xE0,0x00,0x00,0x03,0x7E,0xCC, +// '<' +0x3C,0x09,0x07,0x0A,0x01,0x09, +0x06,0x1C,0x71,0xC7,0x1E,0x1E,0x0E,0x0E,0x0C, +// '=' +0x3D,0x0A,0x09,0x09,0x01,0x0C, +0xFF,0xFF,0xC0,0x00,0x00,0x00,0x03,0xFF,0xFF,0x00,0x00, +// '>' +0x3E,0x08,0x08,0x0B,0x01,0x0A, +0x60,0x70,0x38,0x3C,0x1E,0x0F,0x06,0x0C,0x38,0x70,0xC0, +// '?' +0x3F,0x04,0x0B,0x12,0x01,0x0D, +0x1E,0x0F,0xE3,0xC6,0x60,0x60,0x06,0x00,0xC0,0x18,0x07,0x01,0xE0,0xF8,0x3E,0x0F,0x01,0x80,0x00,0x00,0x01,0x80,0x30,0x06,0x00, +// '@' +0x40,0x02,0x13,0x14,0x01,0x16, +0x03,0xF8,0x01,0xFF,0xC0,0x78,0x3C,0x1C,0x01,0xC3,0x00,0x1C,0xC1,0xC1,0x98,0xF8,0x1E,0x3C,0x03,0xC6,0x30,0x79,0x8E,0x0F,0x31,0xC1,0xE6,0x78,0x6C,0x7F,0xFC,0xC7,0x3E,0x18,0x00,0x01,0x80,0x00,0x38,0x00,0x03,0xC0,0xE0,0x1F,0xFC,0x00,0xFE,0x00, +// 'A' +0x41,0x03,0x0E,0x12,0x01,0x11, +0x00,0x80,0x07,0x00,0x1C,0x00,0xF0,0x03,0xC0,0x1D,0x80,0x76,0x03,0x98,0x0E,0x20,0x70,0xC1,0xFF,0x0F,0xFC,0x7C,0x19,0xC0,0x67,0x01,0xB8,0x07,0xE0,0x0F,0x00,0x30, +// 'B' +0x42,0x03,0x0B,0x13,0x03,0x0F, +0x7C,0x1F,0xE3,0x0E,0x60,0xEC,0x0D,0x81,0xB0,0x36,0x0E,0xC3,0x9F,0xE3,0xFC,0x61,0xEC,0x0F,0x80,0xF0,0x1E,0x0E,0xC7,0xDF,0xE3,0xF0,0x00, +// 'C' +0x43,0x03,0x0D,0x12,0x01,0x0E, +0x01,0xF8,0x3F,0xC3,0xC6,0x38,0x31,0x80,0x1C,0x01,0xC0,0x0C,0x00,0x60,0x06,0x00,0x30,0x01,0x80,0x0C,0x00,0x60,0x19,0x81,0xCE,0x3C,0x3F,0xC0,0xF8,0x00, +// 'D' +0x44,0x03,0x0D,0x12,0x02,0x11, +0x60,0x07,0xC0,0x37,0x81,0x8F,0x0C,0x1C,0x60,0x73,0x01,0xD8,0x06,0xC0,0x1E,0x00,0xF0,0x07,0x80,0x3C,0x01,0xE0,0x1B,0x01,0xDC,0x1C,0xFF,0xC1,0xF8,0x00, +// 'E' +0x45,0x03,0x0D,0x12,0x02,0x0F, +0xFF,0xF7,0xFF,0xF0,0x01,0x80,0x0C,0x00,0x60,0x03,0x00,0x18,0x7E,0xFF,0xF7,0xE0,0x30,0x01,0x80,0x0C,0x00,0x60,0x03,0x00,0x18,0x00,0x7F,0xF1,0xFF,0x80, +// 'F' +0x46,0x03,0x0C,0x12,0x02,0x0F, +0xFF,0xCF,0xFF,0xC0,0x7C,0x00,0xC0,0x0C,0x00,0xC0,0x0D,0xFE,0xFF,0xEF,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0x00,0xC0,0x0C,0x00, +// 'G' +0x47,0x03,0x0F,0x12,0x01,0x10, +0x03,0xE0,0x0F,0xF0,0x38,0xE0,0xE0,0x03,0x80,0x06,0x00,0x18,0x00,0x30,0x00,0x61,0xFF,0x9F,0xFF,0x3C,0x36,0x00,0x6C,0x01,0x98,0x07,0x30,0x0C,0x30,0x70,0x7F,0xC0,0x3E,0x00, +// 'H' +0x48,0x03,0x0F,0x12,0x02,0x12, +0xC0,0x03,0x80,0x0F,0x00,0x1E,0x00,0x3C,0x00,0x78,0x00,0xF0,0x01,0xE0,0x03,0xC0,0xFF,0xFF,0xFF,0xFC,0x1E,0x00,0x3C,0x00,0x78,0x00,0xF0,0x01,0xE0,0x03,0xC0,0x07,0x80,0x0C, +// 'I' +0x49,0x03,0x0C,0x12,0x00,0x0D, +0xFF,0xEF,0xFF,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0xFF,0xFF,0xFF, +// 'J' +0x4A,0x03,0x0E,0x12,0x01,0x10, +0x1F,0xFC,0x7F,0xF0,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0x00,0xC0,0x03,0x00,0x0C,0x00,0x30,0xC0,0xC3,0x06,0x0E,0x18,0x1C,0x60,0x3F,0x80,0x3C,0x00, +// 'K' +0x4B,0x03,0x0C,0x12,0x03,0x0F, +0xC0,0x6C,0x0E,0xC1,0xCC,0x38,0xC7,0x0C,0xE0,0xDC,0x0F,0x80,0xF0,0x0F,0x00,0xF8,0x0F,0xC0,0xDE,0x0C,0xF0,0xC7,0x8C,0x1E,0xC0,0xFC,0x07, +// 'L' +0x4C,0x03,0x0B,0x12,0x01,0x0D, +0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xFF,0xEF,0xFC, +// 'M' +0x4D,0x03,0x13,0x13,0x01,0x15, +0x0C,0x06,0x01,0x80,0xC0,0x78,0x3C,0x0F,0x07,0x81,0xE0,0xF0,0x3C,0x1E,0x07,0x83,0xC1,0xD8,0xEC,0x3B,0x1D,0x87,0x63,0xB0,0xCC,0xE6,0x38,0xDC,0x47,0x1B,0x8C,0xE3,0xF1,0xB8,0x3C,0x37,0x07,0x86,0xE0,0xF0,0x7C,0x1E,0x0F,0x01,0x81,0x80, +// 'N' +0x4E,0x03,0x11,0x12,0x01,0x13, +0x60,0x01,0x38,0x00,0xDE,0x00,0x6F,0x00,0x37,0xC0,0x1B,0x70,0x0D,0x9C,0x06,0xCF,0x03,0x63,0x81,0xB0,0xE0,0xD8,0x38,0x6C,0x0E,0x36,0x03,0x9B,0x00,0xED,0x80,0x3E,0xC0,0x0F,0x60,0x03,0xB0,0x00,0xC0, +// 'O' +0x4F,0x03,0x11,0x12,0x01,0x13, +0x01,0xF8,0x03,0xFF,0x07,0x81,0xC3,0x00,0x63,0x00,0x1B,0x80,0x0D,0x80,0x07,0xC0,0x03,0xC0,0x01,0xE0,0x00,0xF0,0x00,0xF8,0x00,0x6C,0x00,0x33,0x00,0x31,0xC0,0x38,0x70,0x78,0x1F,0xF8,0x03,0xF0,0x00, +// 'P' +0x50,0x03,0x0B,0x12,0x01,0x0D, +0xFE,0x1F,0xF3,0x0F,0x60,0x7C,0x07,0x80,0xF0,0x1E,0x06,0xC3,0xDF,0xF3,0xF8,0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x18,0x00, +// 'Q' +0x51,0x03,0x14,0x17,0x01,0x15, +0x01,0xF8,0x00,0x7F,0xE0,0x1E,0x07,0x03,0x80,0x18,0x30,0x01,0xC6,0x00,0x0C,0x60,0x00,0xEC,0x00,0x06,0xC0,0x00,0x6C,0x00,0x06,0xC0,0x00,0x6C,0x00,0x06,0x60,0xE0,0xE7,0x0F,0x0C,0x38,0x79,0xC1,0xC3,0xF8,0x0F,0xFF,0x00,0x3F,0x78,0x00,0x03,0xC0,0x00,0x1E,0x00,0x00,0xF0,0x00,0x07,0x00,0x00,0x20, +// 'R' +0x52,0x02,0x0D,0x13,0x01,0x0F, +0x00,0x03,0xE0,0x3F,0xC1,0x8F,0x0C,0x0E,0x60,0x33,0x00,0xD8,0x06,0xC0,0x36,0x03,0xB0,0x79,0xFF,0x8F,0xF0,0x7F,0x83,0x1F,0x18,0x3C,0xC0,0xF6,0x01,0xF0,0x06, +// 'S' +0x53,0x03,0x0F,0x13,0x01,0x11, +0x01,0xF0,0x07,0xF8,0x18,0x70,0x60,0x01,0x80,0x03,0x00,0x06,0x00,0x0E,0x00,0x0F,0xF0,0x07,0xF0,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0xD8,0x01,0xB8,0x06,0x78,0x3C,0x7F,0xE0,0x3F,0x00, +// 'T' +0x54,0x02,0x0F,0x13,0x01,0x10, +0x00,0x01,0xFF,0xFD,0xFF,0xF8,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x03,0x00,0x06,0x00,0x0C,0x00,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x03,0x00,0x06,0x00,0x0C,0x00, +// 'U' +0x55,0x03,0x11,0x12,0x01,0x12, +0x60,0x03,0x30,0x01,0x98,0x00,0xCC,0x00,0x66,0x00,0x33,0x00,0x19,0x80,0x0C,0xC0,0x06,0x60,0x03,0x30,0x01,0x98,0x01,0xCC,0x00,0xC7,0x00,0x61,0x80,0x70,0xE0,0x30,0x38,0x38,0x0F,0xF8,0x01,0xF0,0x00, +// 'V' +0x56,0x03,0x0E,0x13,0x02,0x10, +0x80,0x0F,0x00,0x3C,0x01,0xB0,0x06,0x60,0x31,0x80,0xC6,0x03,0x0C,0x18,0x30,0x60,0xC1,0x81,0x8C,0x06,0x30,0x0D,0x80,0x36,0x00,0xF8,0x01,0xC0,0x07,0x00,0x08,0x00,0x00,0x00, +// 'W' +0x57,0x03,0x17,0x12,0x01,0x19, +0xC0,0x20,0x0F,0xC0,0x60,0x19,0x81,0xC0,0x23,0x03,0x80,0xC6,0x07,0x01,0x86,0x1E,0x03,0x0C,0x36,0x0C,0x18,0x6C,0x18,0x11,0x98,0x60,0x33,0x30,0xC0,0x66,0x61,0x80,0xD8,0x66,0x01,0xB0,0xCC,0x01,0xC1,0xB0,0x03,0x83,0x60,0x07,0x07,0x80,0x0C,0x07,0x00,0x08,0x0E,0x00, +// 'X' +0x58,0x03,0x10,0x12,0x01,0x11, +0x60,0x03,0x70,0x07,0x38,0x0E,0x1C,0x1C,0x0C,0x1C,0x0E,0x38,0x07,0x70,0x03,0xE0,0x01,0xC0,0x03,0xC0,0x07,0xE0,0x07,0x70,0x0E,0x38,0x1C,0x18,0x38,0x1C,0x70,0x0E,0xE0,0x07,0xC0,0x03, +// 'Y' +0x59,0x03,0x0F,0x13,0x00,0x10, +0x60,0x06,0xE0,0x1D,0xC0,0x31,0xC0,0xE1,0xC1,0x83,0x83,0x03,0x8C,0x07,0x18,0x07,0x70,0x0F,0xC0,0x0F,0x80,0x0F,0x00,0x1C,0x00,0x38,0x00,0x60,0x01,0xC0,0x03,0x00,0x06,0x00,0x08,0x00, +// 'Z' +0x5A,0x03,0x0F,0x12,0x01,0x11, +0xFF,0xFF,0xFF,0xFC,0x00,0xF0,0x03,0x80,0x0E,0x00,0x3C,0x00,0xF0,0x03,0xC0,0x07,0x00,0x1E,0x00,0x38,0x00,0xE0,0x03,0xC0,0x07,0x00,0x1C,0x00,0x70,0x00,0xFF,0xFF,0xFF,0xFC, +// '[' +0x5B,0x01,0x07,0x1A,0x01,0x09, +0x00,0xFD,0xFB,0x06,0x0C,0x18,0x30,0x60,0xC1,0x83,0x06,0x0C,0x18,0x30,0x60,0xC1,0x83,0x06,0x0C,0x18,0x3F,0x7E,0x00, +// '\' +0x5C,0x03,0x0B,0x14,0x02,0x0D, +0xC0,0x18,0x01,0x80,0x30,0x03,0x00,0x60,0x06,0x00,0xC0,0x0C,0x01,0x80,0x18,0x03,0x00,0x20,0x06,0x00,0xC0,0x0C,0x01,0x80,0x18,0x03,0x00,0x60, +// ']' +0x5D,0x01,0x07,0x1A,0x02,0x09, +0x01,0xFB,0xF0,0x60,0xC1,0x83,0x06,0x0C,0x18,0x30,0x60,0xC1,0x83,0x06,0x0C,0x18,0x30,0x60,0xC1,0x83,0x7E,0xFC,0x00, +// '^' +0x5E,0x02,0x0A,0x06,0x02,0x0E, +0x0C,0x07,0x83,0xF1,0xCE,0xE1,0xF0,0x30, +// '_' +0x5F,0x16,0x0F,0x04,0x00,0x0F, +0x00,0x01,0xFF,0xFF,0xFF,0xF8,0x00,0x00, +// '`' +0x60,0x02,0x05,0x06,0x02,0x0D, +0xC7,0x1C,0x63,0x8C, +// 'a' +0x61,0x09,0x0B,0x0C,0x01,0x0C, +0x0F,0x87,0xF9,0xE3,0x30,0x6E,0x0D,0x81,0xB0,0x36,0x06,0xC0,0xCC,0x39,0xFF,0x9F,0x30, +// 'b' +0x62,0x02,0x0C,0x13,0x01,0x0E, +0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x78,0x7F,0xC7,0x8E,0x60,0x76,0x03,0x60,0x36,0x03,0x60,0x36,0x06,0x70,0xE7,0xFC,0x7F,0x00, +// 'c' +0x63,0x09,0x0A,0x0C,0x01,0x0C, +0x0F,0x07,0xF3,0x0D,0x80,0x60,0x30,0x0C,0x03,0x00,0xC0,0x1C,0x33,0xFC,0x7C, +// 'd' +0x64,0x02,0x0C,0x13,0x01,0x0E, +0x00,0x20,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x61,0xF6,0x3F,0xE7,0x0E,0x60,0x6C,0x06,0xC0,0x6C,0x06,0xC0,0x6E,0x06,0x70,0xE3,0xFE,0x1F,0x60, +// 'e' +0x65,0x09,0x0B,0x0C,0x01,0x0D, +0x1F,0x07,0xF9,0xC7,0x30,0xEC,0x79,0xBE,0x3E,0x07,0x00,0xC0,0x6E,0x1D,0xFF,0x0F,0x80, +// 'f' +0x66,0x02,0x0A,0x14,0x01,0x0C, +0x03,0x83,0xE0,0xE0,0x70,0x18,0x06,0x01,0x83,0xFF,0xFF,0xC6,0x01,0x80,0x60,0x18,0x06,0x01,0x80,0x60,0x18,0x06,0x01,0x80,0x60, +// 'g' +0x67,0x09,0x0A,0x13,0x02,0x0D, +0x0F,0x0F,0xF7,0x0D,0x83,0xC0,0xF0,0x3C,0x1F,0x07,0xC1,0xD8,0xF7,0xEC,0xF3,0x00,0xC0,0x30,0x18,0x06,0x03,0xBF,0xC7,0xE0, +// 'h' +0x68,0x02,0x0B,0x13,0x01,0x0E, +0x60,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x18,0x03,0x1E,0x6F,0xEF,0x8D,0xE1,0xB8,0x36,0x06,0xC0,0xD8,0x1B,0x03,0x60,0x6C,0x0D,0x81,0x80, +// 'i' +0x69,0x04,0x02,0x11,0x03,0x07, +0xF0,0x3F,0xFF,0xFF,0xC0, +// 'j' +0x6A,0x04,0x08,0x18,0x00,0x0A, +0x03,0x03,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0xC3,0xE3,0x77,0x7E,0x1C, +// 'k' +0x6B,0x03,0x0B,0x13,0x02,0x0E, +0xC0,0x18,0x03,0x00,0x60,0x0C,0x01,0x80,0x30,0x36,0x0E,0xC7,0x99,0xE3,0x70,0x7E,0x0F,0xE1,0xCE,0x30,0xE6,0x0E,0xC0,0xF8,0x08,0x00,0x00, +// 'l' +0x6C,0x02,0x02,0x13,0x03,0x07, +0xFF,0xFF,0xFF,0xFF,0xFC, +// 'm' +0x6D,0x09,0x10,0x0C,0x01,0x12, +0x67,0x3C,0x6F,0xFE,0x7D,0xEE,0x79,0x86,0x71,0x86,0x61,0x86,0x61,0x86,0x61,0x86,0x61,0x86,0x61,0x86,0x61,0x86,0x61,0x86, +// 'n' +0x6E,0x09,0x0B,0x0C,0x01,0x0D, +0x63,0x8D,0xF9,0xF1,0xBC,0x37,0x06,0xE0,0xD8,0x1B,0x03,0x60,0x6C,0x0D,0x81,0xB0,0x30, +// 'o' +0x6F,0x09,0x0C,0x0C,0x01,0x0D, +0x0F,0x81,0xFC,0x38,0xC3,0x06,0x60,0x66,0x06,0x60,0x66,0x06,0x60,0xE3,0x1C,0x1F,0x80,0xF0, +// 'p' +0x70,0x08,0x0A,0x14,0x02,0x0D, +0xC0,0x33,0xCF,0xFB,0xC6,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x7C,0x1B,0xFC,0xFE,0x30,0x0C,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00, +// 'q' +0x71,0x08,0x0A,0x14,0x01,0x0C, +0x00,0x03,0xF3,0xFD,0xE3,0x60,0xF8,0x3C,0x0F,0x03,0xC0,0xF0,0x76,0x1D,0xFF,0x1F,0x80,0x60,0x18,0x06,0x01,0x80,0x60,0x18,0x06, +// 'r' +0x72,0x09,0x09,0x0C,0x01,0x0B, +0xCF,0x6F,0xFE,0x7C,0x3C,0x1E,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x00, +// 's' +0x73,0x09,0x09,0x0C,0x02,0x0C, +0x03,0x9F,0xDE,0x7C,0x3E,0x07,0xF0,0xFC,0x07,0x01,0xE0,0xFF,0xC7,0xC0, +// 't' +0x74,0x05,0x0A,0x10,0x00,0x0A, +0x0C,0x03,0x00,0xC0,0x30,0xFF,0xFF,0xF0,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30, +// 'u' +0x75,0x09,0x0B,0x0C,0x01,0x0C, +0xC0,0xD8,0x1B,0x03,0x60,0x6C,0x0D,0x81,0xB0,0x36,0x06,0xC0,0xD8,0x19,0xFF,0x1F,0x60, +// 'v' +0x76,0x09,0x0B,0x0D,0x01,0x0C, +0xC0,0x78,0x1F,0x83,0x30,0x67,0x1C,0x63,0x0C,0xE0,0xD8,0x1E,0x03,0xC0,0x30,0x06,0x00,0x00, +// 'w' +0x77,0x09,0x0F,0x0D,0x01,0x11, +0xC1,0x87,0x83,0x0F,0x0E,0x1E,0x1C,0x66,0x7C,0xCC,0xD9,0x99,0x36,0x36,0x6C,0x7C,0xD8,0x70,0xE0,0xE1,0xC0,0x83,0x80,0x00,0x00, +// 'x' +0x78,0x09,0x0D,0x0D,0x01,0x0E, +0x60,0x1B,0x81,0xCE,0x1C,0x39,0xC0,0xFC,0x03,0xC0,0x3C,0x03,0xF0,0x39,0xC3,0x87,0x38,0x1D,0x80,0x70,0x01,0x80, +// 'y' +0x79,0x09,0x0C,0x13,0x00,0x0D, +0xC0,0x3E,0x07,0x60,0x67,0x0C,0x30,0xC3,0x98,0x19,0x81,0xD8,0x0F,0x00,0xF0,0x06,0x00,0x60,0x0C,0x00,0xC0,0x18,0x01,0x80,0x30,0x03,0x00,0x30,0x00, +// 'z' +0x7A,0x09,0x0B,0x0C,0x01,0x0D, +0xFF,0xFF,0xFC,0x07,0x00,0xC0,0x30,0x0C,0x03,0x80,0xE0,0x38,0x0E,0x03,0xFF,0xFF,0xF0, +// '{' +0x7B,0x02,0x08,0x18,0x01,0x09, +0x0F,0x1F,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x60,0xE0,0xE0,0x70,0x30,0x30,0x30,0x30,0x30,0x38,0x18,0x1F,0x07, +// '|' +0x7C,0x01,0x02,0x18,0x04,0x0A, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +// '}' +0x7D,0x02,0x08,0x18,0x01,0x09, +0x70,0xF8,0x1C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x06,0x07,0x07,0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1C,0x18,0xF8,0xE0, +// '~' +0x7E,0x0B,0x0C,0x05,0x01,0x0E, +0x38,0x37,0xE3,0xE7,0x7C,0x3E,0x01,0xC0, + +// Terminator +0xFF +}; diff --git a/components/epaper/component.mk b/components/epaper/component.mk new file mode 100644 index 0000000..0f76ee5 --- /dev/null +++ b/components/epaper/component.mk @@ -0,0 +1,7 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_SRCDIRS := . +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/components/epaper/dejavuX.c b/components/epaper/dejavuX.c new file mode 100644 index 0000000..c7215e4 --- /dev/null +++ b/components/epaper/dejavuX.c @@ -0,0 +1,718 @@ +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// on Arduinos. It can however handle any ttf font that has been converted +// using the conversion program. These could be fixed width or proportional +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// dejavuX.c +// Point Size : 18 +// Memory usage : 4676 bytes +// # characters : 224 + +// Header Format (to make Arduino UTFT Compatible): +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +const unsigned char tft_Dejavu18[] = +{ +0x00, 0x12, 0x00, 0x00, + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. + +// ' ' +0x20,0x11,0x00,0x00,0x00,0x06, + +// '!' +0x21,0x04,0x02,0x0D,0x03,0x07, +0xFF,0xFF,0xC3,0xC0, +// '"' +0x22,0x04,0x06,0x05,0x01,0x08, +0xCF,0x3C,0xF3,0xCC, +// '#' +0x23,0x03,0x0C,0x0E,0x01,0x0F, +0x04,0x40,0x44,0x0C,0xC0,0xC8,0x7F,0xF7,0xFF,0x09,0x81,0x90,0xFF,0xEF,0xFE,0x13,0x03,0x30,0x32,0x02,0x20, +// '$' +0x24,0x03,0x0A,0x11,0x01,0x0B, +0x08,0x02,0x03,0xE1,0xFC,0xE9,0x32,0x0F,0x81,0xF8,0x0F,0x02,0x60,0x9A,0x2E,0xFF,0x1F,0x80,0x80,0x20,0x08,0x00, +// '%' +0x25,0x04,0x0F,0x0D,0x01,0x11, +0x78,0x10,0x90,0x43,0x31,0x86,0x62,0x0C,0xC8,0x19,0x10,0x1E,0x4F,0x01,0x12,0x02,0x66,0x08,0xCC,0x31,0x98,0x41,0x21,0x03,0xC0, +// '&' +0x26,0x04,0x0C,0x0D,0x01,0x0D, +0x0F,0x01,0xF8,0x30,0x83,0x00,0x38,0x03,0xC0,0x7E,0x6C,0x76,0xC3,0xCC,0x18,0xE1,0xC7,0xFE,0x3E,0x70, +// ''' +0x27,0x04,0x02,0x05,0x01,0x04, +0xFF,0xC0, +// '(' +0x28,0x03,0x04,0x10,0x02,0x07, +0x32,0x66,0x4C,0xCC,0xCC,0xC4,0x66,0x23, +// ')' +0x29,0x03,0x04,0x10,0x01,0x07, +0xC4,0x66,0x23,0x33,0x33,0x32,0x66,0x4C, +// '*' +0x2A,0x04,0x07,0x08,0x01,0x09, +0x11,0x25,0x51,0xC3,0x8A,0xA4,0x88, +// '+' +0x2B,0x05,0x0C,0x0C,0x02,0x0F, +0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x0F,0xFF,0xFF,0xF0,0x60,0x06,0x00,0x60,0x06,0x00,0x60, +// ',' +0x2C,0x0F,0x03,0x04,0x01,0x06, +0x6D,0x40, +// '-' +0x2D,0x0B,0x05,0x02,0x01,0x07, +0xFF,0xC0, +// '.' +0x2E,0x0F,0x02,0x02,0x02,0x06, +0xF0, +// '/' +0x2F,0x04,0x06,0x0F,0x00,0x06, +0x0C,0x31,0x86,0x18,0xE3,0x0C,0x31,0xC6,0x18,0x63,0x0C,0x00, +// '0' +0x30,0x04,0x09,0x0D,0x01,0x0B, +0x3E,0x3F,0x98,0xD8,0x3C,0x1E,0x0F,0x07,0x83,0xC1,0xE0,0xD8,0xCF,0xE3,0xE0, +// '1' +0x31,0x04,0x08,0x0D,0x02,0x0B, +0x38,0xF8,0xD8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0xFF, +// '2' +0x32,0x04,0x09,0x0D,0x01,0x0B, +0x7C,0x7F,0x21,0xC0,0x60,0x30,0x30,0x18,0x18,0x18,0x18,0x18,0x1F,0xEF,0xF0, +// '3' +0x33,0x04,0x09,0x0D,0x01,0x0B, +0x7E,0x7F,0xA0,0xE0,0x30,0x39,0xF0,0xFC,0x07,0x01,0x80,0xE0,0xFF,0xE7,0xE0, +// '4' +0x34,0x04,0x0A,0x0D,0x01,0x0B, +0x07,0x01,0xC0,0xB0,0x6C,0x13,0x08,0xC6,0x31,0x0C,0xFF,0xFF,0xF0,0x30,0x0C,0x03,0x00, +// '5' +0x35,0x04,0x08,0x0D,0x01,0x0B, +0x7E,0x7E,0x60,0x60,0x7C,0x7E,0x47,0x03,0x03,0x03,0x87,0xFE,0x7C, +// '6' +0x36,0x04,0x09,0x0D,0x01,0x0B, +0x1E,0x1F,0x9C,0x5C,0x0C,0x06,0xF3,0xFD,0xC7,0xC1,0xE0,0xD8,0xEF,0xE1,0xE0, +// '7' +0x37,0x04,0x08,0x0D,0x01,0x0B, +0xFF,0xFF,0x06,0x06,0x06,0x0E,0x0C,0x0C,0x1C,0x18,0x18,0x38,0x30, +// '8' +0x38,0x04,0x09,0x0D,0x01,0x0B, +0x3E,0x3F,0xB8,0xF8,0x3E,0x39,0xF1,0xFD,0xC7,0xC1,0xE0,0xF8,0xEF,0xE3,0xE0, +// '9' +0x39,0x04,0x09,0x0D,0x01,0x0B, +0x3C,0x3F,0xB8,0xD8,0x3C,0x1F,0x1D,0xFE,0x7B,0x01,0x81,0xD1,0xCF,0xC3,0xC0, +// ':' +0x3A,0x08,0x02,0x09,0x02,0x06, +0xF0,0x03,0xC0, +// ';' +0x3B,0x08,0x03,0x0B,0x01,0x06, +0x6C,0x00,0x03,0x6A,0x00, +// '<' +0x3C,0x07,0x0B,0x0A,0x02,0x0F, +0x00,0x20,0x3C,0x1F,0x1F,0x0F,0x81,0xF0,0x0F,0x80,0x3E,0x01,0xE0,0x04, +// '=' +0x3D,0x08,0x0B,0x06,0x02,0x0F, +0xFF,0xFF,0xFC,0x00,0x00,0x0F,0xFF,0xFF,0xC0, +// '>' +0x3E,0x07,0x0B,0x0A,0x02,0x0F, +0x80,0x1E,0x01,0xF0,0x07,0xC0,0x3E,0x07,0xC3,0xE3,0xE0,0xF0,0x10,0x00, +// '?' +0x3F,0x04,0x07,0x0D,0x01,0x0A, +0x79,0xFA,0x38,0x30,0x61,0x86,0x18,0x30,0x60,0x01,0x83,0x00, +// '@' +0x40,0x04,0x10,0x10,0x01,0x12, +0x07,0xE0,0x1F,0xF8,0x3C,0x1C,0x70,0x06,0x60,0x07,0xE3,0x63,0xC7,0xE3,0xC6,0x63,0xC6,0x66,0xC7,0xFC,0xE3,0x70,0x60,0x00,0x70,0x00,0x3C,0x30,0x1F,0xF0,0x07,0xC0, +// 'A' +0x41,0x04,0x0C,0x0D,0x00,0x0C, +0x06,0x00,0x60,0x0F,0x00,0xF0,0x19,0x81,0x98,0x19,0x83,0x0C,0x3F,0xC7,0xFE,0x60,0x66,0x06,0xC0,0x30, +// 'B' +0x42,0x04,0x09,0x0D,0x02,0x0C, +0xFC,0x7F,0xB0,0xD8,0x6C,0x37,0xF3,0xF9,0x86,0xC1,0xE0,0xF0,0xFF,0xEF,0xE0, +// 'C' +0x43,0x04,0x0B,0x0D,0x01,0x0D, +0x0F,0xC7,0xFD,0xC0,0xB0,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x0C,0x01,0xC0,0x9F,0xF0,0xFC, +// 'D' +0x44,0x04,0x0B,0x0D,0x02,0x0E, +0xFE,0x1F,0xF3,0x07,0x60,0x6C,0x07,0x80,0xF0,0x1E,0x03,0xC0,0x78,0x1B,0x07,0x7F,0xCF,0xE0, +// 'E' +0x45,0x04,0x08,0x0D,0x02,0x0B, +0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// 'F' +0x46,0x04,0x08,0x0D,0x02,0x0A, +0xFF,0xFF,0xC0,0xC0,0xC0,0xFE,0xFE,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, +// 'G' +0x47,0x04,0x0B,0x0D,0x01,0x0E, +0x0F,0xC7,0xFD,0xC0,0xB0,0x0C,0x01,0x87,0xF0,0xFE,0x03,0xC0,0x6C,0x0D,0xC1,0x9F,0xE0,0xF8, +// 'H' +0x48,0x04,0x0A,0x0D,0x02,0x0E, +0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xFF,0xFF,0xFF,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xC0, +// 'I' +0x49,0x04,0x02,0x0D,0x02,0x06, +0xFF,0xFF,0xFF,0xC0, +// 'J' +0x4A,0x04,0x05,0x11,0xFF,0x06, +0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6,0x31,0x8C,0xFE,0xE0, +// 'K' +0x4B,0x04,0x0B,0x0D,0x02,0x0C, +0xC1,0x98,0x63,0x18,0x66,0x0D,0x81,0xE0,0x3C,0x06,0xC0,0xCC,0x18,0xC3,0x0C,0x60,0xCC,0x0C, +// 'L' +0x4C,0x04,0x08,0x0D,0x02,0x0A, +0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// 'M' +0x4D,0x04,0x0C,0x0D,0x02,0x10, +0xE0,0x7F,0x0F,0xF0,0xFD,0x8B,0xD9,0xBD,0x9B,0xCF,0x3C,0xF3,0xC6,0x3C,0x63,0xC0,0x3C,0x03,0xC0,0x30, +// 'N' +0x4E,0x04,0x0A,0x0D,0x02,0x0E, +0xE0,0xF8,0x3F,0x0F,0xC3,0xD8,0xF6,0x3C,0xCF,0x1B,0xC6,0xF0,0xFC,0x3F,0x07,0xC1,0xC0, +// 'O' +0x4F,0x04,0x0C,0x0D,0x01,0x0E, +0x1F,0x83,0xFC,0x70,0xE6,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x70,0xE3,0xFC,0x1F,0x80, +// 'P' +0x50,0x04,0x08,0x0D,0x02,0x0B, +0xFC,0xFE,0xC7,0xC3,0xC3,0xC7,0xFE,0xFC,0xC0,0xC0,0xC0,0xC0,0xC0, +// 'Q' +0x51,0x04,0x0C,0x0F,0x01,0x0E, +0x1F,0x83,0xFC,0x70,0xE6,0x06,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0xC0,0x36,0x06,0x70,0xE3,0xFC,0x1F,0x80,0x18,0x00,0xC0, +// 'R' +0x52,0x04,0x0A,0x0D,0x02,0x0D, +0xFC,0x3F,0x8C,0x73,0x0C,0xC3,0x31,0xCF,0xE3,0xF0,0xC6,0x30,0xCC,0x33,0x06,0xC1,0xC0, +// 'S' +0x53,0x04,0x0A,0x0D,0x01,0x0B, +0x3E,0x1F,0xCE,0x13,0x00,0xC0,0x1F,0x03,0xF0,0x0E,0x01,0x80,0x68,0x3B,0xFC,0x7E,0x00, +// 'T' +0x54,0x04,0x0C,0x0D,0x00,0x0C, +0xFF,0xFF,0xFF,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00, +// 'U' +0x55,0x04,0x0A,0x0D,0x02,0x0E, +0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x36,0x19,0xFE,0x1E,0x00, +// 'V' +0x56,0x04,0x0C,0x0D,0x00,0x0C, +0xC0,0x36,0x06,0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x19,0x80,0xF0,0x0F,0x00,0x60,0x06,0x00, +// 'W' +0x57,0x04,0x11,0x0D,0x01,0x13, +0xC1,0xC1,0xE0,0xE0,0xD8,0xF8,0xCC,0x6C,0x66,0x36,0x33,0x1B,0x18,0xD8,0xD8,0x6C,0x6C,0x36,0x36,0x1F,0x1F,0x07,0x07,0x03,0x83,0x81,0xC1,0xC0, +// 'X' +0x58,0x04,0x0B,0x0D,0x01,0x0D, +0x70,0xE6,0x18,0xE6,0x0D,0xC0,0xF0,0x1C,0x03,0x80,0x78,0x1B,0x07,0x30,0xC7,0x30,0x6E,0x0E, +// 'Y' +0x59,0x04,0x0C,0x0D,0x00,0x0C, +0xE0,0x76,0x06,0x30,0xC1,0x98,0x19,0x80,0xF0,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00, +// 'Z' +0x5A,0x04,0x0B,0x0D,0x01,0x0D, +0xFF,0xFF,0xFC,0x07,0x01,0xC0,0x30,0x0E,0x03,0x80,0xE0,0x18,0x06,0x01,0xC0,0x7F,0xFF,0xFE, +// '[' +0x5B,0x03,0x04,0x10,0x01,0x07, +0xFF,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xFF, +// '\' +0x5C,0x04,0x06,0x0F,0x00,0x06, +0xC3,0x06,0x18,0x61,0xC3,0x0C,0x30,0xE1,0x86,0x18,0x30,0xC0, +// ']' +0x5D,0x03,0x04,0x10,0x02,0x07, +0xFF,0x33,0x33,0x33,0x33,0x33,0x33,0xFF, +// '^' +0x5E,0x04,0x0B,0x05,0x02,0x0F, +0x0E,0x03,0xE0,0xC6,0x30,0x6C,0x06, +// '_' +0x5F,0x13,0x09,0x02,0x00,0x09, +0xFF,0xFF,0xC0, +// '`' +0x60,0x03,0x04,0x03,0x02,0x09, +0xC6,0x30, +// 'a' +0x61,0x07,0x08,0x0A,0x01,0x0A, +0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// 'b' +0x62,0x03,0x09,0x0E,0x02,0x0B, +0xC0,0x60,0x30,0x18,0x0D,0xE7,0xFB,0x8F,0x83,0xC1,0xE0,0xF0,0x7C,0x7F,0xF6,0xF0, +// 'c' +0x63,0x07,0x08,0x0A,0x01,0x09, +0x1E,0x7F,0x61,0xC0,0xC0,0xC0,0xC0,0x61,0x7F,0x1E, +// 'd' +0x64,0x03,0x09,0x0E,0x01,0x0B, +0x01,0x80,0xC0,0x60,0x33,0xDB,0xFF,0x8F,0x83,0xC1,0xE0,0xF0,0x7C,0x77,0xF9,0xEC, +// 'e' +0x65,0x07,0x0A,0x0A,0x01,0x0B, +0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// 'f' +0x66,0x03,0x07,0x0E,0x00,0x06, +0x1E,0x7C,0xC1,0x8F,0xFF,0xCC,0x18,0x30,0x60,0xC1,0x83,0x06,0x00, +// 'g' +0x67,0x07,0x09,0x0E,0x01,0x0B, +0x3D,0xBF,0xF8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0x7F,0x9E,0xC0,0x68,0x67,0xF1,0xF0, +// 'h' +0x68,0x03,0x08,0x0E,0x02,0x0B, +0xC0,0xC0,0xC0,0xC0,0xDE,0xFE,0xE7,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +// 'i' +0x69,0x03,0x02,0x0E,0x02,0x05, +0xF0,0xFF,0xFF,0xF0, +// 'j' +0x6A,0x03,0x04,0x12,0x00,0x05, +0x33,0x00,0x33,0x33,0x33,0x33,0x33,0x33,0xEC, +// 'k' +0x6B,0x03,0x09,0x0E,0x02,0x0A, +0xC0,0x60,0x30,0x18,0x0C,0x36,0x33,0x31,0xB0,0xF0,0x78,0x36,0x19,0x8C,0x66,0x18, +// 'l' +0x6C,0x03,0x02,0x0E,0x02,0x05, +0xFF,0xFF,0xFF,0xF0, +// 'm' +0x6D,0x07,0x0E,0x0A,0x02,0x11, +0xDC,0x7B,0xFB,0xEE,0x79,0xF0,0xC3,0xC3,0x0F,0x0C,0x3C,0x30,0xF0,0xC3,0xC3,0x0F,0x0C,0x30, +// 'n' +0x6E,0x07,0x08,0x0A,0x02,0x0B, +0xDE,0xFE,0xE7,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +// 'o' +0x6F,0x07,0x0A,0x0A,0x01,0x0B, +0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// 'p' +0x70,0x07,0x09,0x0E,0x02,0x0B, +0xDE,0x7F,0xB8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0xFF,0x6F,0x30,0x18,0x0C,0x06,0x00, +// 'q' +0x71,0x07,0x09,0x0E,0x01,0x0B, +0x3D,0xBF,0xF8,0xF8,0x3C,0x1E,0x0F,0x07,0xC7,0x7F,0x9E,0xC0,0x60,0x30,0x18,0x0C, +// 'r' +0x72,0x07,0x06,0x0A,0x02,0x08, +0xDF,0xFE,0x30,0xC3,0x0C,0x30,0xC3,0x00, +// 's' +0x73,0x07,0x08,0x0A,0x01,0x08, +0x7C,0xFE,0xC2,0xE0,0x7C,0x1E,0x06,0x86,0xFE,0x78, +// 't' +0x74,0x04,0x06,0x0D,0x01,0x07, +0x61,0x86,0x3F,0xFD,0x86,0x18,0x61,0x86,0x1F,0x3C, +// 'u' +0x75,0x07,0x08,0x0A,0x02,0x0B, +0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// 'v' +0x76,0x07,0x0C,0x0A,0x00,0x0B, +0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x19,0x80,0xF0,0x0F,0x00,0x60, +// 'w' +0x77,0x07,0x0F,0x0A,0x01,0x10, +0x63,0x8C,0xC7,0x19,0x8E,0x31,0xB6,0xC3,0x6D,0x86,0xDB,0x0F,0x1E,0x0E,0x38,0x1C,0x70,0x38,0xE0, +// 'x' +0x78,0x07,0x0A,0x0A,0x01,0x0B, +0xE1,0xD8,0x63,0x30,0xCC,0x1E,0x07,0x83,0x30,0xCC,0x61,0xB8,0x70, +// 'y' +0x79,0x07,0x0C,0x0E,0x00,0x0B, +0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x0F,0x00,0xF0,0x06,0x00,0x60,0x06,0x00,0xC0,0x3C,0x03,0x80, +// 'z' +0x7A,0x07,0x08,0x0A,0x01,0x09, +0xFF,0xFF,0x06,0x0C,0x1C,0x38,0x30,0x70,0xFF,0xFF, +// '{' +0x7B,0x03,0x08,0x11,0x02,0x0B, +0x0F,0x1F,0x18,0x18,0x18,0x18,0x38,0xF0,0xF0,0x38,0x18,0x18,0x18,0x18,0x18,0x1F,0x0F, +// '|' +0x7C,0x03,0x02,0x12,0x02,0x06, +0xFF,0xFF,0xFF,0xFF,0xF0, +// '}' +0x7D,0x03,0x08,0x11,0x02,0x0B, +0xF0,0xF8,0x18,0x18,0x18,0x18,0x1C,0x0F,0x0F,0x1C,0x18,0x18,0x18,0x18,0x18,0xF8,0xF0, +// '~' +0x7E,0x08,0x0B,0x05,0x02,0x0F, +0x00,0x0F,0x87,0xFF,0xC3,0xE0,0x00, +// '' +0x7F,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x80,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x81,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x82,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x83,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x84,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x85,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x86,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x87,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x88,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x89,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8A,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8B,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8C,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8D,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8E,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x8F,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x90,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x91,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x92,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x93,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x94,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x95,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x96,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x97,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x98,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x99,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9A,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9B,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9C,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9D,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9E,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0x9F,0x04,0x09,0x10,0x01,0x0B, +0xFF,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0xFF, +// '' +0xA0,0x11,0x00,0x00,0x00,0x06, + +// '' +0xA1,0x07,0x02,0x0D,0x03,0x07, +0xF0,0xFF,0xFF,0xC0, +// '' +0xA2,0x04,0x08,0x10,0x02,0x0B, +0x04,0x04,0x04,0x1E,0x7F,0x75,0xC4,0xC4,0xC4,0xC4,0x65,0x7F,0x1E,0x04,0x04,0x04, +// '' +0xA3,0x04,0x09,0x0D,0x01,0x0B, +0x0F,0x0F,0xCE,0x26,0x03,0x01,0x83,0xF9,0xFC,0x30,0x18,0x0C,0x1F,0xFF,0xF8, +// '' +0xA4,0x06,0x0A,0x0A,0x00,0x0B, +0x00,0x10,0x13,0x58,0xFE,0x11,0x0C,0x61,0x10,0xFE,0x35,0x90,0x10, +// '' +0xA5,0x04,0x0A,0x0D,0x01,0x0B, +0xC0,0xD8,0x66,0x18,0xCC,0x33,0x3F,0xF1,0xE0,0x30,0xFF,0xC3,0x00,0xC0,0x30,0x0C,0x00, +// '' +0xA6,0x04,0x02,0x10,0x02,0x06, +0xFF,0xFC,0x3F,0xFF, +// '' +0xA7,0x04,0x07,0x0F,0x01,0x09, +0x3C,0xF9,0x83,0x03,0x0F,0x33,0x63,0x66,0x78,0x60,0x60,0xCF,0x9E,0x00, +// '' +0xA8,0x03,0x06,0x02,0x02,0x09, +0xCF,0x30, +// '' +0xA9,0x04,0x0D,0x0D,0x02,0x12, +0x0F,0x81,0x83,0x18,0x0C,0x8F,0x28,0x80,0xC8,0x06,0x40,0x32,0x01,0x88,0x8A,0x38,0x98,0x0C,0x60,0xC0,0xF8,0x00, +// '' +0xAA,0x04,0x08,0x09,0x01,0x08, +0x7C,0x02,0x7E,0xC2,0x82,0xC6,0x7A,0x00,0xFE, +// '' +0xAB,0x08,0x08,0x08,0x01,0x0B, +0x11,0x33,0x66,0xCC,0xCC,0x66,0x33,0x11, +// '' +0xAC,0x09,0x0B,0x05,0x02,0x0F, +0xFF,0xFF,0xFC,0x01,0x80,0x30,0x06, +// '' +0xAD,0x0B,0x05,0x02,0x01,0x07, +0xFF,0xC0, +// '' +0xAE,0x04,0x0D,0x0D,0x02,0x12, +0x0F,0x81,0x83,0x18,0x0C,0x9E,0x28,0x88,0xC4,0x46,0x3C,0x31,0x21,0x88,0x8A,0x46,0x98,0x0C,0x60,0xC0,0xF8,0x00, +// '' +0xAF,0x04,0x05,0x02,0x02,0x09, +0xFF,0xC0, +// '' +0xB0,0x04,0x06,0x06,0x02,0x09, +0x7B,0x38,0x61,0xCD,0xE0, +// '' +0xB1,0x06,0x0C,0x0B,0x02,0x0F, +0x06,0x00,0x60,0x06,0x0F,0xFF,0xFF,0xF0,0x60,0x06,0x00,0x60,0x00,0x0F,0xFF,0xFF,0xF0, +// '' +0xB2,0x04,0x05,0x07,0x01,0x07, +0x74,0x42,0x22,0x23,0xE0, +// '' +0xB3,0x04,0x05,0x07,0x01,0x07, +0xF0,0x5C,0x30,0x8F,0xC0, +// '' +0xB4,0x03,0x04,0x03,0x03,0x09, +0x36,0xC0, +// '' +0xB5,0x07,0x09,0x0E,0x02,0x0B, +0xC3,0x61,0xB0,0xD8,0x6C,0x36,0x1B,0x0D,0xCE,0xFF,0xEC,0xF0,0x18,0x0C,0x06,0x00, +// '' +0xB6,0x04,0x08,0x0F,0x01,0x0B, +0x3F,0x79,0xF9,0xF9,0xF9,0xF9,0x79,0x09,0x09,0x09,0x09,0x09,0x09,0x09,0x09, +// '' +0xB7,0x0A,0x02,0x02,0x02,0x06, +0xF0, +// '' +0xB8,0x11,0x04,0x03,0x03,0x09, +0x23,0xF0, +// '' +0xB9,0x04,0x05,0x07,0x01,0x07, +0xE1,0x08,0x42,0x13,0xE0, +// '' +0xBA,0x04,0x07,0x09,0x01,0x08, +0x38,0x8A,0x0C,0x18,0x28,0x8E,0x00,0xFE, +// '' +0xBB,0x08,0x08,0x08,0x02,0x0B, +0x88,0xCC,0x66,0x33,0x33,0x66,0xCC,0x88, +// '' +0xBC,0x04,0x10,0x0D,0x01,0x11, +0xE0,0x18,0x20,0x10,0x20,0x20,0x20,0x60,0x20,0x40,0x20,0x80,0xF9,0x86,0x01,0x0A,0x02,0x12,0x06,0x22,0x04,0x3F,0x08,0x02,0x18,0x02, +// '' +0xBD,0x04,0x0F,0x0D,0x01,0x11, +0xE0,0x18,0x40,0x20,0x80,0x81,0x03,0x02,0x04,0x04,0x10,0x3E,0x67,0x00,0x91,0x02,0x02,0x0C,0x08,0x10,0x20,0x40,0x81,0x83,0xE0, +// '' +0xBE,0x04,0x10,0x0D,0x01,0x11, +0xF0,0x18,0x08,0x10,0x70,0x20,0x18,0x60,0x08,0x40,0x18,0x80,0xF1,0x86,0x01,0x0A,0x02,0x12,0x06,0x22,0x04,0x3F,0x08,0x02,0x18,0x02, +// '' +0xBF,0x07,0x07,0x0E,0x02,0x0A, +0x18,0x30,0x00,0xC1,0x83,0x0C,0x18,0x61,0x83,0x07,0x17,0xE7,0x80, +// '' +0xC0,0x01,0x0C,0x10,0x00,0x0C, +0x06,0x00,0x30,0x00,0x00,0x60,0x06,0x00,0xF0,0x0F,0x01,0x98,0x19,0x81,0x98,0x30,0xC3,0xFC,0x7F,0xE6,0x06,0x60,0x6C,0x03, +// '' +0xC1,0x01,0x0C,0x10,0x00,0x0C, +0x03,0x00,0x60,0x00,0x00,0x60,0x06,0x00,0xF0,0x0F,0x01,0x98,0x19,0x81,0x98,0x30,0xC3,0xFC,0x7F,0xE6,0x06,0x60,0x6C,0x03, +// '' +0xC2,0x01,0x0C,0x10,0x00,0x0C, +0x06,0x00,0x90,0x00,0x00,0x60,0x06,0x00,0xF0,0x0F,0x01,0x98,0x19,0x81,0x98,0x30,0xC3,0xFC,0x7F,0xE6,0x06,0x60,0x6C,0x03, +// '' +0xC3,0x01,0x0C,0x10,0x00,0x0C, +0x0C,0x81,0x30,0x00,0x00,0x60,0x06,0x00,0xF0,0x0F,0x01,0x98,0x19,0x81,0x98,0x30,0xC3,0xFC,0x7F,0xE6,0x06,0x60,0x6C,0x03, +// '' +0xC4,0x01,0x0C,0x10,0x00,0x0C, +0x19,0x81,0x98,0x00,0x00,0x60,0x06,0x00,0xF0,0x0F,0x01,0x98,0x19,0x81,0x98,0x30,0xC3,0xFC,0x7F,0xE6,0x06,0x60,0x6C,0x03, +// '' +0xC5,0x00,0x0C,0x11,0x00,0x0C, +0x06,0x00,0x90,0x09,0x00,0x90,0x06,0x00,0x60,0x0F,0x00,0xF0,0x19,0x81,0x98,0x19,0x83,0x0C,0x3F,0xC7,0xFE,0x60,0x66,0x06,0xC0,0x30, +// '' +0xC6,0x04,0x10,0x0D,0x00,0x11, +0x07,0xFF,0x07,0xFF,0x06,0xC0,0x0C,0xC0,0x0C,0xC0,0x18,0xFF,0x18,0xFF,0x30,0xC0,0x3F,0xC0,0x3F,0xC0,0x60,0xC0,0x60,0xFF,0xC0,0xFF, +// '' +0xC7,0x04,0x0B,0x10,0x01,0x0D, +0x0F,0xC7,0xFD,0xC0,0xB0,0x0C,0x01,0x80,0x30,0x06,0x00,0xC0,0x0C,0x01,0xC0,0x9F,0xF0,0xFC,0x02,0x00,0x60,0x3C, +// '' +0xC8,0x01,0x08,0x10,0x02,0x0B, +0x30,0x18,0x00,0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// '' +0xC9,0x01,0x08,0x10,0x02,0x0B, +0x18,0x30,0x00,0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// '' +0xCA,0x01,0x08,0x10,0x02,0x0B, +0x38,0x6C,0x00,0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// '' +0xCB,0x01,0x08,0x10,0x02,0x0B, +0x66,0x66,0x00,0xFF,0xFF,0xC0,0xC0,0xC0,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,0xFF,0xFF, +// '' +0xCC,0x01,0x05,0x10,0x00,0x06, +0x61,0x80,0x63,0x18,0xC6,0x31,0x8C,0x63,0x18,0xC6, +// '' +0xCD,0x01,0x04,0x10,0x01,0x06, +0x6C,0x06,0x66,0x66,0x66,0x66,0x66,0x66, +// '' +0xCE,0x01,0x06,0x10,0x00,0x06, +0x31,0x20,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C, +// '' +0xCF,0x01,0x06,0x10,0x00,0x06, +0xCF,0x30,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC3,0x0C, +// '' +0xD0,0x04,0x0D,0x0D,0x00,0x0E, +0x3F,0x81,0xFF,0x0C,0x1C,0x60,0x73,0x01,0xFF,0x0F,0xF8,0x66,0x03,0x30,0x19,0x81,0xCC,0x1C,0x7F,0xC3,0xF8,0x00, +// '' +0xD1,0x01,0x0A,0x10,0x02,0x0E, +0x19,0x09,0x80,0x03,0x83,0xE0,0xFC,0x3F,0x0F,0x63,0xD8,0xF3,0x3C,0x6F,0x1B,0xC3,0xF0,0xFC,0x1F,0x07, +// '' +0xD2,0x01,0x0C,0x10,0x01,0x0E, +0x06,0x00,0x30,0x00,0x01,0xF8,0x3F,0xC7,0x0E,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x67,0x0E,0x3F,0xC1,0xF8, +// '' +0xD3,0x01,0x0C,0x10,0x01,0x0E, +0x03,0x00,0x60,0x00,0x01,0xF8,0x3F,0xC7,0x0E,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x67,0x0E,0x3F,0xC1,0xF8, +// '' +0xD4,0x01,0x0C,0x10,0x01,0x0E, +0x06,0x00,0x90,0x00,0x01,0xF8,0x3F,0xC7,0x0E,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x67,0x0E,0x3F,0xC1,0xF8, +// '' +0xD5,0x01,0x0C,0x10,0x01,0x0E, +0x0C,0x81,0x30,0x00,0x01,0xF8,0x3F,0xC7,0x0E,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x67,0x0E,0x3F,0xC1,0xF8, +// '' +0xD6,0x01,0x0C,0x10,0x01,0x0E, +0x19,0x81,0x98,0x00,0x01,0xF8,0x3F,0xC7,0x0E,0x60,0x6C,0x03,0xC0,0x3C,0x03,0xC0,0x3C,0x03,0x60,0x67,0x0E,0x3F,0xC1,0xF8, +// '' +0xD7,0x06,0x0A,0x0A,0x02,0x0F, +0x40,0xB8,0x77,0x38,0xFC,0x1E,0x07,0x83,0xF1,0xCE,0xE1,0xD0,0x20, +// '' +0xD8,0x03,0x0E,0x0F,0x00,0x0E, +0x00,0x00,0x3E,0x21,0xFD,0x0E,0x1C,0x70,0x71,0x83,0x66,0x19,0x98,0xC6,0x66,0x19,0xB0,0x63,0x83,0x8E,0x1C,0x2F,0xE1,0x1F,0x00,0x00,0x00, +// '' +0xD9,0x01,0x0A,0x10,0x02,0x0E, +0x18,0x03,0x00,0x03,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xD8,0x67,0xF8,0x78, +// '' +0xDA,0x01,0x0A,0x10,0x02,0x0E, +0x0C,0x06,0x00,0x03,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xD8,0x67,0xF8,0x78, +// '' +0xDB,0x01,0x0A,0x10,0x02,0x0E, +0x0C,0x04,0x80,0x03,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xD8,0x67,0xF8,0x78, +// '' +0xDC,0x01,0x0A,0x10,0x02,0x0E, +0x33,0x0C,0xC0,0x03,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xF0,0x3C,0x0F,0x03,0xC0,0xD8,0x67,0xF8,0x78, +// '' +0xDD,0x01,0x0C,0x10,0x00,0x0C, +0x06,0x00,0xC0,0x00,0x0E,0x07,0x60,0x63,0x0C,0x19,0x81,0x98,0x0F,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60,0x06,0x00,0x60, +// '' +0xDE,0x04,0x08,0x0D,0x02,0x0B, +0xC0,0xC0,0xFC,0xFE,0xC7,0xC3,0xC3,0xC7,0xFE,0xFC,0xC0,0xC0,0xC0, +// '' +0xDF,0x03,0x09,0x0E,0x02,0x0B, +0x3C,0x3F,0x39,0xD8,0x6C,0x66,0x63,0x31,0x8C,0xC3,0x60,0xF0,0x7A,0x7D,0xF6,0x70, +// '' +0xE0,0x03,0x08,0x0E,0x01,0x0A, +0x30,0x18,0x0C,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE1,0x03,0x08,0x0E,0x01,0x0A, +0x06,0x0C,0x18,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE2,0x03,0x08,0x0E,0x01,0x0A, +0x18,0x3C,0x66,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE3,0x03,0x08,0x0E,0x01,0x0A, +0x32,0x4C,0x00,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE4,0x03,0x08,0x0E,0x01,0x0A, +0x66,0x66,0x00,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE5,0x01,0x08,0x10,0x01,0x0A, +0x1C,0x22,0x22,0x22,0x1C,0x00,0x3C,0x7E,0x47,0x03,0x3F,0xFF,0xC3,0xC7,0xFF,0x7B, +// '' +0xE6,0x07,0x0F,0x0A,0x01,0x11, +0x3C,0x78,0xFD,0xF9,0x1E,0x38,0x18,0x33,0xFF,0xFF,0xFF,0xF0,0xC0,0x63,0xC1,0xFD,0xFE,0xF0,0xF8, +// '' +0xE7,0x07,0x08,0x0D,0x01,0x09, +0x1E,0x7F,0x61,0xC0,0xC0,0xC0,0xC0,0x61,0x7F,0x1E,0x04,0x06,0x1E, +// '' +0xE8,0x03,0x0A,0x0E,0x01,0x0B, +0x30,0x06,0x00,0xC0,0x00,0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// '' +0xE9,0x03,0x0A,0x0E,0x01,0x0B, +0x06,0x03,0x01,0x80,0x00,0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// '' +0xEA,0x03,0x0A,0x0E,0x01,0x0B, +0x0C,0x07,0x83,0x30,0x00,0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// '' +0xEB,0x03,0x0A,0x0E,0x01,0x0B, +0x33,0x0C,0xC0,0x00,0x00,0x1F,0x1F,0xE6,0x1F,0x03,0xFF,0xFF,0xFC,0x01,0x81,0x7F,0xC7,0xE0, +// '' +0xEC,0x03,0x04,0x0E,0x00,0x05, +0xC6,0x30,0x33,0x33,0x33,0x33,0x33, +// '' +0xED,0x03,0x04,0x0E,0x01,0x05, +0x36,0xC0,0x66,0x66,0x66,0x66,0x66, +// '' +0xEE,0x03,0x06,0x0E,0x00,0x05, +0x31,0xEC,0xC0,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC0, +// '' +0xEF,0x03,0x06,0x0E,0x00,0x05, +0xCF,0x30,0x00,0x30,0xC3,0x0C,0x30,0xC3,0x0C,0x30,0xC0, +// '' +0xF0,0x03,0x0A,0x0E,0x01,0x0B, +0x10,0x07,0xC7,0xC0,0x18,0x1F,0x1F,0xE6,0x1F,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF1,0x03,0x08,0x0E,0x02,0x0B, +0x32,0x4C,0x00,0x00,0xDE,0xFE,0xE7,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +// '' +0xF2,0x03,0x0A,0x0E,0x01,0x0B, +0x30,0x06,0x00,0xC0,0x00,0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF3,0x03,0x0A,0x0E,0x01,0x0B, +0x06,0x03,0x01,0x80,0x00,0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF4,0x03,0x0A,0x0E,0x01,0x0B, +0x0C,0x07,0x83,0x30,0x00,0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF5,0x03,0x0A,0x0E,0x01,0x0B, +0x19,0x09,0x80,0x00,0x00,0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF6,0x03,0x0A,0x0E,0x01,0x0B, +0x33,0x0C,0xC0,0x00,0x00,0x1E,0x1F,0xE6,0x1B,0x03,0xC0,0xF0,0x3C,0x0D,0x86,0x7F,0x87,0x80, +// '' +0xF7,0x07,0x0C,0x08,0x02,0x0F, +0x06,0x00,0x60,0x00,0x0F,0xFF,0xFF,0xF0,0x00,0x06,0x00,0x60, +// '' +0xF8,0x06,0x0C,0x0C,0x00,0x0B, +0x00,0x00,0xF2,0x1F,0xC3,0x0C,0x61,0xE6,0x26,0x64,0x67,0x86,0x30,0xC3,0xFC,0x4F,0x00,0x00, +// '' +0xF9,0x03,0x08,0x0E,0x02,0x0B, +0x60,0x30,0x18,0x00,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// '' +0xFA,0x03,0x08,0x0E,0x02,0x0B, +0x0C,0x18,0x30,0x00,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// '' +0xFB,0x03,0x08,0x0E,0x02,0x0B, +0x18,0x3C,0x66,0x00,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// '' +0xFC,0x03,0x08,0x0E,0x02,0x0B, +0x66,0x66,0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xE7,0x7F,0x7B, +// '' +0xFD,0x03,0x0C,0x12,0x00,0x0B, +0x03,0x00,0x60,0x0C,0x00,0x00,0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x0F,0x00,0xF0,0x06,0x00,0x60,0x06,0x00,0xC0,0x3C,0x03,0x80, +// '' +0xFE,0x03,0x09,0x12,0x02,0x0B, +0xC0,0x60,0x30,0x18,0x0D,0xE7,0xFB,0x8F,0x83,0xC1,0xE0,0xF0,0x7C,0x7F,0xF6,0xF3,0x01,0x80,0xC0,0x60,0x00, +// '' +0xFF,0x03,0x0C,0x12,0x00,0x0B, +0x1B,0x01,0xB0,0x00,0x00,0x00,0x60,0x66,0x06,0x30,0xC3,0x0C,0x19,0x81,0x98,0x0F,0x00,0xF0,0x06,0x00,0x60,0x06,0x00,0xC0,0x3C,0x03,0x80, + +// Terminator +0xFF +}; diff --git a/components/epaper/minya24.c b/components/epaper/minya24.c new file mode 100644 index 0000000..6bab953 --- /dev/null +++ b/components/epaper/minya24.c @@ -0,0 +1,331 @@ +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// on Arduinos. It can however handle any ttf font that has been converted +// using the conversion program. These could be fixed width or proportional +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// minya24.c +// Point Size : 24 +// Memory usage : 2807 bytes +// # characters : 95 + +// Header Format (to make Arduino UTFT Compatible): +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +unsigned char tft_minya24[] = +{ +0x00, 0x15, 0x00, 0x00, + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. + +// ' ' +0x20,0x13,0x00,0x00,0x00,0x07, + +// '!' +0x21,0x02,0x05,0x12,0x00,0x05, +0x11,0x8C,0x63,0x18,0xC6,0x31,0x8C,0x42,0x01,0xCE,0x73,0x80, +// '"' +0x22,0x01,0x06,0x08,0x00,0x06, +0x01,0xA6,0xDB,0x6D,0xB6,0xC0, +// '#' +0x23,0x04,0x0C,0x0E,0x00,0x0B, +0x04,0x80,0x6C,0x0C,0x80,0xD8,0x7F,0xE7,0xFE,0x1B,0x01,0xB0,0x7F,0xE7,0xFC,0x12,0x03,0x20,0x32,0x00,0x00, +// '$' +0x24,0x02,0x0A,0x11,0x00,0x0B, +0x04,0x01,0x61,0xF8,0xFE,0x65,0x19,0x06,0x40,0xF0,0x1F,0x01,0xE0,0x4C,0x93,0x7C,0xCF,0xE0,0x60,0x10,0x04,0x00, +// '%' +0x25,0x01,0x0D,0x14,0x01,0x0F, +0x00,0x01,0xC1,0x1F,0x19,0x8C,0xCC,0x6C,0x63,0x61,0x36,0x0F,0xB0,0x1B,0x00,0x18,0x01,0x80,0x0C,0x00,0xCE,0x06,0xF8,0x6C,0x66,0x63,0x33,0x13,0x0F,0x98,0x38,0x00,0x00, +// '&' +0x26,0x02,0x0E,0x11,0x00,0x0D, +0x0E,0x00,0x7C,0x01,0xB0,0x06,0xC0,0x1E,0x00,0x38,0x00,0xC3,0x07,0x8C,0x37,0x60,0xCD,0x86,0x1E,0x18,0x70,0x41,0xE1,0x07,0xC6,0x33,0x9F,0x86,0x38,0x00, +// ''' +0x27,0x02,0x03,0x08,0x00,0x03, +0x6D,0xB6,0xD8, +// '(' +0x28,0x01,0x05,0x14,0x02,0x07, +0x00,0x8E,0x66,0x33,0x18,0xC6,0x31,0x8C,0x61,0x0C,0x63,0x8C,0x00, +// ')' +0x29,0x01,0x06,0x15,0x00,0x07, +0x01,0x86,0x0C,0x18,0x61,0x82,0x08,0x30,0xC2,0x08,0x61,0x86,0x30,0xC6,0x10,0x00, +// '*' +0x2A,0x04,0x0A,0x0D,0x01,0x0B, +0x08,0x03,0x04,0xC1,0xF6,0x7F,0x07,0x81,0xC0,0xF8,0x3F,0x1B,0xCC,0xD8,0x30,0x0C,0x00, +// '+' +0x2B,0x06,0x0A,0x0A,0x01,0x0B, +0x00,0x03,0x00,0xC0,0x30,0x7F,0xBF,0xE0,0xC0,0x30,0x0C,0x00,0x00, +// ',' +0x2C,0x10,0x05,0x07,0x00,0x05, +0x33,0x9C,0x63,0x20,0x00, +// '-' +0x2D,0x09,0x07,0x02,0x00,0x07, +0x7D,0xF8, +// '.' +0x2E,0x10,0x04,0x04,0x01,0x05, +0x6E,0xE6, +// '/' +0x2F,0x01,0x0C,0x13,0x00,0x0B, +0x00,0x00,0x06,0x00,0x60,0x0C,0x00,0xC0,0x18,0x01,0x80,0x30,0x03,0x00,0x60,0x06,0x00,0xC0,0x0C,0x01,0x80,0x18,0x03,0x00,0x30,0x06,0x00,0x40,0x00, +// '0' +0x30,0x08,0x0B,0x0B,0x00,0x0B, +0x0E,0x03,0xE0,0xC6,0x30,0x66,0x0C,0xC0,0x98,0x33,0x06,0x61,0xC7,0xF0,0x7C,0x00, +// '1' +0x31,0x08,0x0A,0x0D,0x00,0x09, +0x04,0x1F,0x03,0xC0,0x30,0x0C,0x03,0x00,0xC0,0x30,0x0C,0x03,0x07,0xF9,0xFE,0x00,0x00, +// '2' +0x32,0x06,0x0A,0x0E,0x00,0x0A, +0x0E,0x07,0xC3,0x30,0xCC,0x03,0x01,0x80,0x60,0x30,0x18,0x0C,0x03,0x01,0xF8,0x7F,0xC0,0x20, +// '3' +0x33,0x08,0x09,0x10,0x00,0x08, +0x78,0x3F,0x81,0x81,0x81,0xC0,0xC0,0xE0,0x78,0x06,0x01,0x01,0x80,0xC0,0xE1,0xE0,0xC0,0x00, +// '4' +0x34,0x03,0x0B,0x13,0x00,0x0A, +0x00,0x00,0xC0,0x18,0x03,0x00,0x60,0x08,0x03,0x00,0x60,0x0D,0x83,0x20,0x64,0x18,0x83,0x10,0xC6,0x1F,0xF3,0xFE,0x03,0x00,0x60,0x0C,0x00, +// '5' +0x35,0x09,0x0A,0x0E,0x00,0x0A, +0x3F,0x8F,0xE3,0x00,0x80,0x60,0x1F,0x87,0xF0,0x06,0x01,0x80,0x60,0x19,0x86,0x7F,0x07,0x80, +// '6' +0x36,0x03,0x0A,0x12,0x00,0x0A, +0x00,0x01,0x80,0xE0,0x30,0x18,0x06,0x03,0x00,0xC0,0x60,0x1B,0x87,0xF1,0x86,0x61,0x90,0x66,0x19,0x86,0x3F,0x07,0x80, +// '7' +0x37,0x09,0x0A,0x0E,0x00,0x0A, +0x7F,0x9F,0xF0,0x18,0x0C,0x06,0x01,0x80,0xC0,0x30,0x18,0x06,0x01,0x80,0x40,0x10,0x0C,0x00, +// '8' +0x38,0x03,0x0B,0x11,0x00,0x0B, +0x0F,0x07,0xF0,0xC3,0x30,0x66,0x0C,0x63,0x87,0xE0,0xF8,0x39,0xCE,0x19,0x81,0x30,0x34,0x06,0xC1,0x98,0x71,0xFC,0x1F,0x00, +// '9' +0x39,0x07,0x0A,0x11,0x00,0x0A, +0x1E,0x0F,0xC6,0x19,0x86,0x41,0x98,0x66,0x18,0xFE,0x1D,0x80,0x40,0x30,0x0C,0x06,0x03,0x01,0xC0,0x20,0x00,0x00, +// ':' +0x3A,0x0A,0x05,0x0A,0x00,0x05, +0x33,0x9C,0x60,0x00,0xCE,0x71,0x80, +// ';' +0x3B,0x09,0x05,0x0D,0x00,0x06, +0x33,0xDE,0x60,0x00,0x0E,0x73,0x8C,0xC0,0x00, +// '<' +0x3C,0x06,0x0A,0x0C,0x00,0x09, +0x00,0x00,0xE0,0x70,0x38,0x1C,0x1C,0x03,0x00,0x70,0x0E,0x01,0xC0,0x10,0x00, +// '=' +0x3D,0x09,0x08,0x06,0x01,0x0A, +0xFF,0xFE,0x00,0x00,0x7F,0xFF, +// '>' +0x3E,0x06,0x0A,0x0C,0x00,0x09, +0x00,0x18,0x03,0x80,0x70,0x0E,0x00,0xE0,0x30,0x38,0x1C,0x0E,0x02,0x00,0x00, +// '?' +0x3F,0x02,0x09,0x12,0x00,0x09, +0x1E,0x1F,0x98,0x6C,0x30,0x18,0x18,0x38,0x30,0x30,0x18,0x0C,0x02,0x01,0x00,0x00,0x60,0x78,0x3C,0x0C,0x00, +// '@' +0x40,0x02,0x11,0x11,0x00,0x11, +0x01,0xF0,0x03,0xFE,0x03,0x03,0x83,0x00,0xC3,0x04,0x31,0x1F,0x19,0x9F,0x0C,0xCC,0x86,0x4C,0x43,0x24,0x21,0x12,0x39,0x89,0xF7,0x84,0x71,0x83,0x00,0x00,0xC1,0x80,0x3F,0xC0,0x0F,0x80,0x00, +// 'A' +0x41,0x02,0x12,0x13,0x00,0x10, +0x00,0x00,0x01,0xF0,0x00,0x7E,0x00,0x03,0x80,0x01,0xA0,0x00,0x6C,0x00,0x1B,0x00,0x0C,0x40,0x03,0x18,0x01,0x86,0x00,0x61,0x80,0x1F,0xE0,0x0F,0xFC,0x03,0x03,0x01,0x80,0xC0,0x60,0x10,0x18,0x1F,0x9F,0xC7,0xEF,0xF8,0x00, +// 'B' +0x42,0x02,0x0E,0x13,0x00,0x0E, +0x7F,0x81,0xFF,0x81,0x86,0x06,0x18,0x18,0xC0,0x66,0x01,0xFC,0x07,0xFC,0x1C,0x30,0x60,0x61,0x81,0x86,0x06,0x18,0x18,0x60,0x61,0x81,0x06,0x0C,0x18,0xE0,0xFE,0x03,0xE0,0x00, +// 'C' +0x43,0x02,0x0F,0x12,0x00,0x0F, +0x03,0xF0,0x1F,0xE0,0x70,0xC1,0x80,0x83,0x00,0x04,0x00,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x03,0x00,0x06,0x00,0x06,0x00,0x0E,0x01,0x0E,0x0E,0x0F,0xF8,0x07,0xE0, +// 'D' +0x44,0x02,0x0F,0x13,0x00,0x0F, +0x3F,0xC0,0x7F,0xE0,0x60,0xE0,0xC0,0xC1,0x80,0xC3,0x01,0x86,0x03,0x0C,0x02,0x18,0x04,0x30,0x08,0x60,0x30,0xC0,0x61,0x80,0x83,0x03,0x06,0x0C,0x0C,0x38,0x18,0xE0,0xFF,0x81,0xF8,0x00, +// 'E' +0x45,0x02,0x0D,0x13,0x00,0x0D, +0x3F,0xF1,0xFF,0x86,0x0C,0x30,0x61,0x80,0x0C,0x40,0x62,0x03,0xF0,0x1F,0x80,0xC4,0x06,0x20,0x30,0x01,0x80,0x0C,0x00,0x60,0xC3,0x06,0x7F,0xF3,0xFF,0x80,0x00, +// 'F' +0x46,0x01,0x0E,0x13,0x00,0x0E, +0x00,0x01,0xFF,0xE7,0xFF,0x86,0x06,0x18,0x18,0x63,0x01,0x8C,0x03,0xF0,0x0F,0xC0,0x63,0x01,0x8C,0x06,0x30,0x18,0x00,0x60,0x01,0x80,0x06,0x00,0x3F,0x81,0xFE,0x00,0x00,0x00, +// 'G' +0x47,0x01,0x11,0x14,0x00,0x11, +0x00,0x00,0x03,0xC8,0x03,0xFC,0x03,0x0E,0x03,0x03,0x01,0x00,0x01,0x80,0x00,0xC0,0x00,0x60,0x03,0x30,0x3F,0xD8,0x1F,0x0C,0x01,0x86,0x00,0xC3,0x80,0x60,0xC0,0x30,0x70,0x38,0x1C,0x3C,0x07,0xF6,0x01,0xF3,0x80,0x01,0xE0, +// 'H' +0x48,0x01,0x10,0x13,0x00,0x10, +0x00,0x00,0x7C,0x0F,0x1C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x08,0x0C,0x08,0x0F,0xF8,0x0F,0xF8,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x1A,0x7C,0x1F,0x7C,0x10, +// 'I' +0x49,0x02,0x08,0x13,0x00,0x08, +0x7E,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E, +// 'J' +0x4A,0x01,0x0C,0x14,0x00,0x0B, +0x00,0x00,0x7E,0x07,0xE0,0x18,0x01,0x80,0x18,0x01,0x80,0x18,0x00,0x80,0x08,0x00,0x80,0x18,0x61,0x84,0x18,0x41,0x8C,0x18,0xC3,0x06,0x70,0x7E,0x01,0x80, +// 'K' +0x4B,0x02,0x11,0x13,0x00,0x10, +0x7E,0x7E,0x3F,0x3E,0x06,0x06,0x03,0x06,0x01,0x86,0x00,0xC6,0x00,0x66,0x00,0x37,0x00,0x1F,0x80,0x0E,0x60,0x06,0x10,0x03,0x0C,0x01,0x06,0x00,0x81,0x00,0x60,0x80,0x30,0x66,0x7E,0x1B,0x3F,0x0F,0x80,0x01,0x80, +// 'L' +0x4C,0x02,0x0D,0x11,0x00,0x0D, +0x7E,0x03,0xF0,0x06,0x00,0x30,0x01,0x80,0x0C,0x00,0x60,0x03,0x00,0x18,0x00,0xC0,0x06,0x00,0x30,0x01,0x81,0x0C,0x08,0x60,0x4F,0xFF,0x7F,0xF8, +// 'M' +0x4D,0x02,0x14,0x12,0x00,0x13, +0x7C,0x07,0x87,0xC0,0x7C,0x0C,0x06,0x00,0xE0,0xE0,0x0E,0x0E,0x00,0xE0,0xE0,0x1B,0x1E,0x01,0xB1,0x60,0x0B,0x16,0x00,0x9B,0x60,0x09,0xA6,0x00,0x9E,0x60,0x08,0xE6,0x00,0x8C,0x60,0x08,0xC6,0x03,0x8C,0x7E,0x78,0x47,0xE0,0x00,0x00, +// 'N' +0x4E,0x01,0x12,0x13,0x00,0x12, +0x00,0x00,0x1F,0x03,0xF3,0xC0,0x7C,0x38,0x10,0x0E,0x04,0x03,0xC1,0x00,0xB0,0x40,0x26,0x10,0x09,0x84,0x02,0x31,0x00,0x8C,0x40,0x21,0x90,0x08,0x74,0x06,0x0F,0x01,0x81,0xC0,0x60,0x70,0x78,0x0C,0x3E,0x03,0x00,0x00,0x40, +// 'O' +0x4F,0x02,0x11,0x12,0x00,0x11, +0x03,0xE0,0x07,0xFC,0x07,0x07,0x06,0x01,0x82,0x00,0x63,0x00,0x11,0x80,0x0C,0x80,0x06,0x40,0x03,0x20,0x01,0x98,0x00,0xCC,0x00,0x66,0x00,0x31,0x80,0x30,0xE0,0x18,0x38,0x18,0x0F,0xF8,0x01,0xF0,0x00, +// 'P' +0x50,0x02,0x0C,0x12,0x00,0x0C, +0x7F,0x07,0xFC,0x10,0x61,0x03,0x10,0x31,0x03,0x10,0x31,0x06,0x10,0xE1,0xFC,0x1E,0x01,0x00,0x18,0x01,0x80,0x18,0x01,0x80,0x7E,0x07,0xF0, +// 'Q' +0x51,0x02,0x13,0x15,0x00,0x11, +0x07,0xE0,0x01,0xFE,0x00,0x60,0x70,0x18,0x06,0x06,0x00,0x60,0xC0,0x0C,0x18,0x00,0xC2,0x00,0x18,0x40,0x03,0x08,0x0C,0x61,0x83,0xCC,0x30,0xCD,0x83,0x09,0xE0,0x60,0x3C,0x06,0x07,0x00,0x7F,0xE3,0x07,0xEC,0x40,0x01,0x98,0x00,0x33,0x00,0x03,0xC0,0x00,0x30, +// 'R' +0x52,0x02,0x0F,0x13,0x00,0x0F, +0x7F,0x80,0xFF,0xE0,0x60,0xE0,0xC0,0x61,0x80,0xC3,0x01,0x86,0x07,0x0C,0x1C,0x1F,0xF0,0x3F,0x80,0x66,0x00,0xC4,0x01,0x8C,0x63,0x18,0xC6,0x11,0x8C,0x33,0x7E,0x7C,0xFC,0x70,0x00,0x00, +// 'S' +0x53,0x01,0x0D,0x13,0x00,0x0D, +0x00,0x60,0x7B,0x07,0xF8,0x71,0xC3,0x06,0x18,0x00,0xC0,0x03,0x80,0x0E,0x00,0x1C,0x00,0x78,0x00,0xC0,0x03,0x30,0x19,0x80,0xCC,0x06,0x30,0x70,0xFF,0x03,0xE0, +// 'T' +0x54,0x01,0x0F,0x12,0x00,0x0F, +0x00,0x00,0xFF,0xF9,0xFF,0xF3,0x18,0x66,0x30,0xCC,0x61,0x90,0xC3,0x01,0x80,0x03,0x00,0x06,0x00,0x0C,0x00,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x0F,0xE0,0x1F,0xC0, +// 'U' +0x55,0x02,0x10,0x11,0x00,0x0F, +0x7C,0x7E,0x7C,0x7E,0x30,0x08,0x30,0x08,0x30,0x08,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x10,0x08,0x18,0x18,0x18,0x18,0x0C,0x30,0x0F,0xF0,0x03,0xC0, +// 'V' +0x56,0x01,0x10,0x12,0x00,0x10, +0x00,0x7E,0xFE,0x7E,0x7E,0x18,0x18,0x30,0x18,0x30,0x18,0x20,0x08,0x60,0x0C,0x60,0x0C,0x60,0x0C,0x40,0x0C,0xC0,0x04,0xC0,0x04,0xC0,0x06,0x80,0x07,0x80,0x07,0x80,0x03,0x00,0x03,0x00, +// 'W' +0x57,0x01,0x14,0x13,0x00,0x14, +0x00,0x00,0x0F,0xC4,0xFF,0xF0,0x4F,0xE3,0x06,0x10,0x30,0xE3,0x01,0x8E,0x30,0x18,0xE3,0x01,0x8E,0x30,0x19,0xA3,0x01,0x9A,0x20,0x09,0xA6,0x00,0xD2,0x60,0x0D,0x36,0x00,0xF3,0x40,0x0F,0x34,0x00,0x63,0xC0,0x06,0x1C,0x00,0x61,0x80,0x00,0x18,0x00, +// 'X' +0x58,0x01,0x10,0x13,0x00,0x10, +0x00,0x00,0x7F,0x7F,0x7E,0x7E,0x0C,0x30,0x06,0x20,0x06,0x60,0x03,0xC0,0x01,0x80,0x01,0x80,0x03,0x80,0x02,0xC0,0x06,0x40,0x04,0x60,0x0C,0x60,0x18,0x30,0x18,0x30,0x7C,0x30,0xFC,0xFE,0x00,0xFF, +// 'Y' +0x59,0x01,0x11,0x13,0x00,0x10, +0x00,0x00,0x7F,0x3F,0xBF,0x07,0x03,0x03,0x00,0xC1,0x00,0x31,0x80,0x18,0x80,0x06,0xC0,0x01,0xC0,0x00,0xE0,0x00,0x30,0x00,0x30,0x00,0x18,0x00,0x0C,0x00,0x06,0x00,0x03,0x00,0x07,0x80,0x03,0xF8,0x00,0x7C,0x00, +// 'Z' +0x5A,0x02,0x0E,0x12,0x00,0x0D, +0x7F,0xF9,0xFF,0xE6,0x03,0x18,0x18,0x60,0xC1,0x07,0x00,0x18,0x00,0xC0,0x06,0x00,0x18,0x00,0xC0,0x06,0x00,0x18,0x30,0xC0,0xC3,0x03,0x1F,0xFE,0x7F,0xF8,0x00,0x60, +// '[' +0x5B,0x01,0x08,0x14,0x01,0x08, +0x00,0x7C,0x7C,0x40,0x40,0x40,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x7E,0x00, +// '\' +0x5C,0x01,0x0A,0x13,0x00,0x0A, +0x00,0x10,0x06,0x01,0x80,0x30,0x0C,0x01,0x80,0x60,0x0C,0x03,0x00,0x60,0x18,0x06,0x00,0xC0,0x30,0x06,0x01,0x80,0x60,0x08, +// ']' +0x5D,0x01,0x08,0x14,0x00,0x08, +0x00,0x7C,0x7E,0x04,0x04,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x7C,0xFC, +// '^' +0x5E,0x03,0x0B,0x0B,0x00,0x0B, +0x00,0x00,0x40,0x18,0x03,0x00,0xF0,0x32,0x0C,0x63,0x86,0x60,0xC0,0x0C,0x00,0x00, +// '_' +0x5F,0x15,0x11,0x02,0xFF,0x0F, +0x7F,0xFF,0x3F,0xFF,0x80, +// '`' +0x60,0x00,0x06,0x07,0x00,0x06, +0x01,0x87,0x0C,0x18,0x20,0x00, +// 'a' +0x61,0x06,0x0C,0x0D,0x00,0x0B, +0x0E,0x03,0xF0,0x31,0x06,0x10,0x23,0x80,0xF8,0x3D,0x87,0x10,0x61,0x0C,0x18,0x63,0xA7,0xFE,0x1C,0xC0, +// 'b' +0x62,0x01,0x0C,0x14,0x00,0x0C, +0x00,0x07,0xC0,0x3C,0x01,0x80,0x18,0x01,0x80,0x18,0x01,0x98,0x1F,0xC1,0xC6,0x18,0x21,0x83,0x10,0x31,0x03,0x18,0x31,0x83,0x18,0x6F,0xC6,0xF7,0xC0,0x30, +// 'c' +0x63,0x06,0x0B,0x0D,0x00,0x0B, +0x06,0x03,0xF0,0xC7,0x31,0xE4,0x1C,0x80,0x30,0x06,0x00,0x60,0x0C,0x01,0xC3,0x1F,0xC0,0xF0, +// 'd' +0x64,0x01,0x0D,0x13,0x00,0x0D, +0x00,0x00,0x1E,0x00,0xF0,0x01,0x80,0x0C,0x00,0x60,0x03,0x01,0x98,0x3E,0x81,0x9C,0x18,0x60,0xC3,0x04,0x08,0x60,0x41,0x02,0x0C,0x30,0x71,0x91,0xFF,0x87,0x1C, +// 'e' +0x65,0x07,0x0B,0x0C,0x00,0x0B, +0x0F,0x03,0xF0,0xC3,0x30,0x66,0x1C,0xDE,0x1E,0x03,0x00,0x60,0x06,0x18,0x7E,0x07,0x80, +// 'f' +0x66,0x02,0x0A,0x12,0x00,0x08, +0x07,0x07,0xE1,0xB8,0x4E,0x10,0x04,0x07,0xE1,0xF8,0x18,0x06,0x01,0x80,0x60,0x18,0x06,0x01,0x81,0xF8,0x7E,0x00,0x00, +// 'g' +0x67,0x05,0x0A,0x12,0x00,0x0A, +0x00,0x00,0x20,0x18,0x7E,0x3F,0x18,0x64,0x09,0x02,0x61,0x9F,0xC1,0xE0,0x0C,0x01,0x84,0x67,0x19,0x8C,0x7E,0x0F,0x00, +// 'h' +0x68,0x02,0x0E,0x12,0x00,0x0D, +0x78,0x01,0xE0,0x01,0x80,0x06,0x00,0x19,0xC0,0x2F,0x80,0xE6,0x07,0x08,0x18,0x20,0x60,0x81,0x82,0x06,0x08,0x18,0x20,0x60,0x81,0x82,0x1F,0x9E,0x7E,0x78,0x00,0x00, +// 'i' +0x69,0x02,0x08,0x11,0x00,0x08, +0x30,0x78,0x78,0x30,0x00,0x78,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7F, +// 'j' +0x6A,0x02,0x09,0x15,0xFE,0x07, +0x06,0x07,0x03,0x81,0xC0,0x00,0xF8,0x7C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x01,0x99,0x8F,0xC3,0xC0, +// 'k' +0x6B,0x02,0x0D,0x12,0x00,0x0D, +0x78,0x03,0xC0,0x06,0x00,0x37,0xC1,0xBC,0x0C,0xC0,0x6C,0x03,0xC0,0x1C,0x00,0xC0,0x07,0x00,0x3E,0x01,0xB8,0x08,0x61,0xC1,0xCF,0x07,0x00,0x10,0x00,0x00, +// 'l' +0x6C,0x02,0x08,0x12,0x00,0x08, +0x78,0x78,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,0x00, +// 'm' +0x6D,0x08,0x14,0x0D,0x00,0x14, +0x40,0xC3,0x0F,0xBE,0xF8,0x1E,0x78,0xC1,0xC7,0x0C,0x18,0x20,0xC1,0x82,0x0C,0x10,0x20,0xC1,0x82,0x08,0x18,0x60,0x81,0x84,0x18,0x7E,0x71,0xE7,0xE7,0x9E,0x00,0x00,0x00, +// 'n' +0x6E,0x06,0x0E,0x0E,0x00,0x0D, +0x79,0xC1,0xEF,0x81,0xE6,0x07,0x08,0x18,0x30,0x60,0xC1,0x82,0x06,0x08,0x18,0x20,0x60,0x81,0x82,0x1F,0x1E,0x7E,0x78,0x00,0x00, +// 'o' +0x6F,0x08,0x0B,0x0C,0x00,0x0C, +0x0F,0x03,0xF8,0xC3,0x30,0x36,0x06,0x80,0xD0,0x1B,0x03,0x60,0x66,0x18,0xFE,0x07,0x80, +// 'p' +0x70,0x07,0x0B,0x10,0x00,0x0B, +0x07,0x1F,0xF1,0xE3,0x18,0x23,0x06,0x60,0xCC,0x19,0x82,0x30,0xC6,0x18,0xFE,0x1B,0x83,0x00,0x60,0x1C,0x07,0x80, +// 'q' +0x71,0x06,0x0D,0x11,0x00,0x0B, +0x0C,0x01,0xF7,0x0C,0xF8,0xC3,0x04,0x18,0x20,0x41,0x02,0x08,0x10,0x60,0x83,0x04,0x0C,0x60,0x7F,0x01,0xE8,0x00,0xC0,0x06,0xC0,0x1E,0x00,0xE0, +// 'r' +0x72,0x08,0x0A,0x0D,0x00,0x09, +0x77,0x3F,0xE3,0xB8,0xCE,0x33,0x0C,0x03,0x00,0xC0,0x30,0x04,0x07,0xC1,0xF0,0x00,0x00, +// 's' +0x73,0x05,0x0A,0x0E,0x00,0x0A, +0x03,0x00,0xC1,0xF0,0xFC,0x63,0x18,0x47,0x00,0xF8,0x07,0x80,0x64,0x19,0x86,0x7F,0x07,0x80, +// 't' +0x74,0x02,0x09,0x13,0x00,0x09, +0x30,0x18,0x0C,0x06,0x02,0x07,0xE3,0xF8,0x40,0x20,0x10,0x08,0x04,0x02,0x11,0x18,0x84,0x62,0x33,0x0F,0x83,0x00, +// 'u' +0x75,0x06,0x0E,0x0E,0x00,0x0D, +0x00,0x03,0xE7,0xC7,0x9F,0x0C,0x30,0x30,0xC0,0xC3,0x03,0x0C,0x0C,0x30,0x30,0xC0,0xC3,0x03,0x0C,0x0E,0x7C,0x1F,0xF8,0x3C,0x00, +// 'v' +0x76,0x07,0x0E,0x0D,0x00,0x0D, +0x01,0xFB,0xF3,0xEF,0xC6,0x06,0x18,0x18,0x60,0x31,0x00,0xCC,0x01,0x30,0x06,0x80,0x0A,0x00,0x38,0x00,0xE0,0x01,0x00, +// 'w' +0x77,0x08,0x11,0x0D,0x00,0x11, +0x04,0x1F,0xFF,0x6F,0xBE,0x31,0x83,0x1C,0xC1,0x9E,0x40,0x4D,0x20,0x34,0xB0,0x1A,0x78,0x05,0x3C,0x03,0x8E,0x01,0x86,0x00,0x43,0x00,0x00,0x00, +// 'x' +0x78,0x08,0x0D,0x0D,0x00,0x0D, +0x0C,0xFB,0xE7,0xDE,0x18,0x31,0x80,0xD8,0x03,0x80,0x18,0x01,0xE0,0x19,0x81,0x8C,0x0E,0x7D,0xF3,0xE4,0x00,0x00, +// 'y' +0x79,0x06,0x0F,0x11,0x00,0x0D, +0x7C,0x01,0xF8,0x00,0xC3,0xF0,0xC7,0xE1,0x82,0x01,0x0C,0x03,0x18,0x06,0x20,0x06,0xC0,0x0D,0x00,0x0E,0x00,0x18,0x00,0x30,0x06,0xC0,0x1F,0x00,0x3E,0x00,0x30,0x00, +// 'z' +0x7A,0x08,0x0B,0x0B,0x00,0x0B, +0x7F,0xCF,0xF9,0x86,0x31,0x86,0x60,0x1C,0x07,0x00,0xC6,0x30,0xCF,0xF9,0xFF,0x00, +// '{' +0x7B,0x02,0x09,0x12,0x00,0x08, +0x07,0x07,0x83,0x01,0x01,0x80,0xC0,0x60,0xE0,0x70,0x0C,0x06,0x03,0x01,0x80,0xC0,0x60,0x30,0x1F,0x03,0x80, +// '|' +0x7C,0x02,0x03,0x12,0x01,0x05, +0x49,0x24,0x92,0x6D,0xB6,0xDB,0x6C, +// '}' +0x7D,0x02,0x07,0x11,0x00,0x07, +0x30,0xF0,0x20,0x41,0x83,0x02,0x06,0x0E,0x18,0x60,0xC0,0x81,0x83,0x3C,0x78, +// '~' +0x7E,0x09,0x0D,0x04,0x00,0x0D, +0x18,0x03,0xF1,0x98,0xFC,0x83,0xC0, + +// Terminator +0xFF +}; \ No newline at end of file diff --git a/components/epaper/tooney32.c b/components/epaper/tooney32.c new file mode 100644 index 0000000..753fb2d --- /dev/null +++ b/components/epaper/tooney32.c @@ -0,0 +1,331 @@ +// This comes with no warranty, implied or otherwise + +// This data structure was designed to support Proportional fonts +// on Arduinos. It can however handle any ttf font that has been converted +// using the conversion program. These could be fixed width or proportional +// fonts. Individual characters do not have to be multiples of 8 bits wide. +// Any width is fine and does not need to be fixed. + +// The data bits are packed to minimize data requirements, but the tradeoff +// is that a header is required per character. + +// tooney32.c +// Point Size : 32 +// Memory usage : 5470 bytes +// # characters : 95 + +// Header Format (to make Arduino UTFT Compatible): +// ------------------------------------------------ +// Character Width (Used as a marker to indicate use this format. i.e.: = 0x00) +// Character Height +// First Character (Reserved. 0x00) +// Number Of Characters (Reserved. 0x00) + +unsigned char tft_tooney32[] = +{ +0x00, 0x20, 0x00, 0x00, + +// Individual Character Format: +// ---------------------------- +// Character Code +// Adjusted Y Offset +// Width +// Height +// xOffset +// xDelta (the distance to move the cursor. Effective width of the character.) +// Data[n] + +// NOTE: You can remove any of these characters if they are not needed in +// your application. The first character number in each Glyph indicates +// the ASCII character code. Therefore, these do not have to be sequential. +// Just remove all the content for a particular character to save space. + +// ' ' +0x20,0x1E,0x00,0x00,0x00,0x09, + +// '!' +0x21,0x09,0x0B,0x16,0x00,0x0B, +0x3F,0xC8,0x07,0x81,0x70,0x27,0x00,0xE0,0x1C,0x21,0x84,0x30,0x86,0x10,0xC2,0x18,0x43,0xF0,0x61,0x0C,0x13,0x02,0x60,0x4E,0x09,0xE2,0x1F,0x81,0xE0,0x00,0x00, +// '"' +0x22,0x05,0x0E,0x0A,0xFF,0x0D, +0x04,0x30,0x2D,0x61,0x8C,0x44,0x71,0x31,0x88,0xCE,0x42,0x72,0x18,0xC8,0x7B,0xC0,0xC6,0x00, +// '#' +0x23,0x07,0x18,0x16,0x00,0x18, +0x00,0xFF,0xF8,0x01,0x83,0x08,0x01,0x82,0x08,0x01,0x82,0x08,0x0F,0x06,0x0F,0x10,0x00,0x01,0x30,0x00,0x01,0x30,0x00,0x00,0x20,0x00,0x02,0x7E,0x0C,0x1E,0x7E,0x0C,0x1E,0x60,0x00,0x02,0x60,0x00,0x00,0x40,0x00,0x04,0x40,0x00,0x04,0xC0,0x00,0x04,0xFC,0x10,0x78,0xFC,0x30,0x78,0x08,0x30,0x40,0x18,0x30,0x40,0x1F,0xFF,0x80,0x1F,0xFF,0x80, +// '$' +0x24,0x09,0x0F,0x14,0x00,0x0F, +0x01,0x80,0x04,0xF8,0x18,0x08,0x20,0x10,0xC0,0x41,0x00,0x86,0x01,0x0C,0x12,0x18,0x38,0x30,0x60,0xA0,0x83,0x01,0x06,0x02,0x0C,0x04,0x18,0x10,0x30,0x20,0x6E,0x40,0xFF,0x81,0x9E,0x00,0x18,0x00, +// '%' +0x25,0x07,0x17,0x16,0x00,0x17, +0x0F,0x81,0xF8,0x20,0x84,0x10,0x80,0x98,0x42,0x00,0x21,0x0C,0x01,0x82,0x18,0x43,0x08,0x30,0x04,0x10,0x70,0x18,0x40,0xE0,0x21,0x00,0xF1,0x82,0x00,0xFF,0x0F,0xC0,0xFC,0x10,0x40,0x18,0x40,0x40,0x21,0x80,0x40,0x82,0x00,0x83,0x0C,0x21,0x04,0x18,0x02,0x18,0x78,0x04,0x21,0x30,0x10,0xC2,0x78,0xC3,0xF8,0x7F,0x07,0xE0,0x7C,0x00, +// '&' +0x26,0x08,0x17,0x17,0x00,0x17, +0x01,0xF6,0x00,0x04,0x1A,0x00,0x10,0x04,0x00,0x40,0x10,0x01,0x00,0x40,0x06,0x00,0x80,0x0C,0x1A,0x00,0x18,0x1F,0xC0,0x30,0x18,0xF8,0x60,0x08,0x09,0x80,0x00,0x33,0x00,0x00,0xCC,0x0C,0x01,0x18,0x3C,0x04,0x30,0x30,0x07,0x60,0x00,0x01,0xE0,0x00,0x07,0xC0,0x00,0x09,0xC0,0x0C,0x23,0xC0,0x38,0x03,0xE1,0xF8,0x03,0xFF,0x70,0x01,0xF8,0x60,0x00, +// ''' +0x27,0x05,0x09,0x0A,0xFF,0x08, +0x06,0x05,0x86,0x23,0x13,0x11,0x90,0x90,0xC8,0x78,0x18,0x00, +// '(' +0x28,0x05,0x0D,0x1D,0x00,0x0D, +0x03,0x00,0x34,0x01,0x90,0x08,0x40,0x81,0x88,0x1C,0xC1,0xC4,0x08,0x60,0x83,0x04,0x10,0x41,0x82,0x0C,0x10,0x60,0x83,0x04,0x18,0x20,0xC0,0x06,0x04,0x38,0x20,0xC0,0x87,0x06,0x38,0x1C,0xE0,0x67,0x86,0x1C,0x60,0x76,0x01,0xE0,0x0E,0x00,0x60,0x00, +// ')' +0x29,0x05,0x0D,0x1D,0x00,0x0D, +0x01,0x00,0x10,0x01,0x20,0x11,0x03,0x04,0x30,0x11,0xE0,0x8F,0x82,0x1C,0x10,0x70,0x81,0x82,0x0E,0x10,0x30,0x81,0x84,0x0C,0x20,0x61,0x02,0x08,0x10,0x01,0x84,0x08,0x20,0x81,0x18,0x11,0x80,0x8E,0x08,0x78,0x80,0xE4,0x03,0xE0,0x0E,0x00,0x30,0x00, +// '*' +0x2A,0x09,0x0C,0x0D,0x01,0x0D, +0x07,0x00,0x88,0x18,0xE4,0x11,0xC0,0x1F,0x8E,0xC0,0x1C,0x11,0xC8,0xBF,0x8E,0x7D,0xC1,0xE0,0x0C,0x00, +// '+' +0x2B,0x09,0x15,0x14,0x00,0x15, +0x00,0x7C,0x00,0x04,0x10,0x00,0x60,0x80,0x07,0x04,0x00,0x38,0x20,0x01,0xC1,0x00,0xFE,0x0F,0xCC,0x00,0x01,0xE0,0x00,0x0F,0x00,0x00,0x78,0x00,0x03,0xFF,0x07,0xFF,0xF8,0x3F,0x7F,0xC1,0xF0,0x0E,0x08,0x00,0x70,0x40,0x03,0x82,0x00,0x1F,0xF0,0x00,0xFE,0x00,0x03,0xE0,0x00, +// ',' +0x2C,0x17,0x09,0x0B,0x00,0x09, +0x1E,0x10,0x98,0x38,0x1C,0x0F,0x0B,0xC4,0xE4,0x32,0x1E,0x0E,0x00, +// '-' +0x2D,0x11,0x09,0x06,0x00,0x09, +0x1B,0x90,0x50,0x39,0x2F,0xE7,0xF0, +// '.' +0x2E,0x16,0x09,0x09,0x00,0x09, +0x1E,0x10,0x90,0x38,0x1C,0x0F,0x07,0xCC,0xFC,0x3C,0x00, +// '/' +0x2F,0x09,0x11,0x19,0x00,0x11, +0x00,0x3F,0x80,0x30,0x40,0x30,0x20,0x18,0x20,0x18,0x10,0x0C,0x08,0x06,0x08,0x06,0x04,0x03,0x04,0x03,0x02,0x01,0x81,0x00,0xC1,0x00,0xC0,0x80,0x60,0x00,0x60,0x40,0x30,0x20,0x18,0x20,0x18,0x10,0x0C,0x08,0x06,0x08,0x06,0x04,0x03,0x04,0x03,0xFE,0x01,0xFE,0x00,0xFE,0x00,0x00, +// '0' +0x30,0x08,0x17,0x17,0x00,0x17, +0x00,0x7E,0x00,0x03,0x01,0x80,0x18,0x00,0x80,0x40,0x00,0x81,0x00,0x00,0x86,0x00,0x00,0x08,0x00,0x01,0x30,0x00,0x02,0x40,0x3C,0x03,0x80,0xFC,0x07,0x02,0x3C,0x0E,0x04,0x38,0x1C,0x08,0x30,0x3C,0x08,0x40,0x78,0x0F,0x01,0x30,0x00,0x02,0x70,0x00,0x08,0xF0,0x00,0x10,0xF0,0x00,0xC0,0xF0,0x03,0x00,0xFC,0x1C,0x00,0xFF,0xE0,0x00,0x3F,0x00,0x00, +// '1' +0x31,0x09,0x0D,0x16,0x00,0x0D, +0x00,0x30,0x06,0x40,0xC4,0x18,0x43,0x02,0x20,0x13,0xC0,0x9E,0x04,0x30,0x21,0x81,0x0C,0x08,0x60,0x43,0x02,0x18,0x10,0xC0,0x86,0x04,0x30,0x21,0x81,0x98,0x03,0xFF,0xE7,0xFE,0x00,0x00, +// '2' +0x32,0x08,0x12,0x17,0x00,0x12, +0x00,0xF0,0x00,0x81,0x00,0xC0,0x20,0xE0,0x04,0x40,0x01,0x38,0x00,0x2F,0x84,0x08,0xF9,0x82,0x1F,0xE0,0x81,0xF0,0x00,0x1C,0x10,0x06,0x04,0x01,0x03,0x80,0xC0,0xD0,0x20,0x04,0x18,0x01,0x0C,0x00,0x43,0x00,0x11,0x80,0x04,0x60,0x01,0x3F,0xFF,0x4F,0xFF,0xE0,0x00,0x30, +// '3' +0x33,0x08,0x12,0x17,0x00,0x12, +0x0C,0x00,0x05,0xFF,0xE3,0x00,0x08,0xC0,0x02,0x30,0x01,0x0C,0x00,0x43,0x00,0x10,0xC0,0x0C,0x37,0x01,0x0F,0x80,0x23,0xE0,0x08,0x10,0x01,0x0F,0xE0,0x43,0xF8,0x10,0x4E,0x04,0x23,0x01,0x10,0x00,0x08,0x00,0x24,0x00,0x13,0x00,0x08,0xFC,0x0C,0x1F,0xFE,0x00,0xFE,0x00, +// '4' +0x34,0x09,0x12,0x16,0x00,0x12, +0x00,0x0E,0x00,0x04,0x80,0x06,0x20,0x03,0x08,0x01,0x82,0x00,0xC0,0x80,0x40,0x20,0x20,0x08,0x10,0x03,0x88,0x20,0x94,0x00,0x07,0x00,0x01,0xC0,0x00,0x70,0x00,0x1C,0x00,0x07,0xFE,0x09,0xFF,0x83,0x80,0x60,0xC0,0x30,0x10,0x0F,0xF8,0x03,0xFC,0x00,0x00,0x00, +// '5' +0x35,0x08,0x11,0x17,0x00,0x11, +0x00,0x03,0x00,0xFF,0x40,0x80,0x20,0xC0,0x10,0x60,0x08,0x30,0x04,0x30,0x02,0x18,0x1D,0x0C,0x07,0x06,0x01,0x82,0x00,0x43,0x00,0x11,0xFC,0x08,0xFF,0x04,0x17,0x82,0x18,0x01,0x18,0x00,0x08,0x00,0x8C,0x00,0x8C,0x00,0xC7,0xC1,0xC3,0xFF,0x80,0xBF,0x00, +// '6' +0x36,0x08,0x13,0x17,0x00,0x13, +0x00,0x7F,0xC0,0x30,0x08,0x18,0x01,0x06,0x00,0x41,0x80,0x10,0x60,0x02,0x08,0x0C,0x83,0x00,0x30,0x60,0x01,0x18,0x00,0x13,0x00,0x01,0x60,0x00,0x0C,0x00,0x03,0x80,0xC0,0x78,0x3C,0x0F,0x03,0x01,0x60,0x00,0x0E,0x00,0x08,0xE0,0x02,0x1E,0x00,0x81,0xF0,0x60,0x1F,0xF8,0x00,0xFC,0x00, +// '7' +0x37,0x08,0x12,0x17,0x00,0x12, +0x0C,0x00,0x0D,0xFF,0xF3,0x00,0x04,0xC0,0x02,0x30,0x00,0x8C,0x00,0x43,0x00,0x10,0xDC,0x08,0x3F,0x02,0x0E,0x81,0x00,0x60,0x40,0x10,0x20,0x0C,0x08,0x02,0x04,0x01,0x81,0x00,0x40,0x80,0x30,0x20,0x10,0x10,0x0F,0x04,0x03,0xF1,0x00,0x3F,0x40,0x03,0xE0,0x00,0x30,0x00, +// '8' +0x38,0x08,0x12,0x17,0x00,0x12, +0x01,0xF0,0x00,0x83,0x00,0xC0,0x20,0x20,0x08,0x10,0x01,0x0C,0x18,0x43,0x06,0x10,0xC0,0x04,0x38,0x01,0x0C,0x00,0x42,0x00,0x09,0x80,0x01,0xC0,0x00,0x70,0x3C,0x1C,0x0F,0x07,0x00,0x01,0xE0,0x00,0x9C,0x00,0x27,0x80,0x30,0xF8,0x38,0x1F,0xFC,0x01,0xFC,0x00,0x00,0x00, +// '9' +0x39,0x08,0x13,0x17,0x00,0x13, +0x01,0xF8,0x00,0xC0,0xC0,0x20,0x04,0x08,0x00,0x42,0x00,0x08,0xC0,0x00,0x90,0x18,0x16,0x07,0x81,0xC0,0x60,0x38,0x00,0x07,0x80,0x00,0xF0,0x00,0x17,0x00,0x02,0xF0,0x00,0x0F,0x80,0x10,0xE6,0x02,0x08,0x00,0x83,0x00,0x20,0x40,0x0C,0x10,0x03,0x06,0xC1,0xC0,0xFF,0xE0,0x1F,0xF0,0x00, +// ':' +0x3A,0x0E,0x09,0x11,0x00,0x09, +0x0E,0x10,0x90,0x38,0x1C,0x0F,0x07,0xC4,0xFC,0x3C,0x19,0x98,0x38,0x1C,0x0F,0x07,0xCC,0xFC,0x3C,0x00, +// ';' +0x3B,0x0F,0x09,0x13,0x00,0x09, +0x0E,0x10,0x98,0x38,0x1C,0x0F,0x07,0xC4,0xFC,0x3E,0x19,0x98,0x38,0x1C,0x0F,0x03,0xC4,0xE4,0x32,0x1E,0x0E,0x00, +// '<' +0x3C,0x0A,0x13,0x13,0x00,0x13, +0x00,0x00,0xC0,0x00,0x64,0x00,0x60,0x80,0x30,0x10,0x38,0x02,0x18,0x03,0x8C,0x01,0xE3,0x01,0xF0,0xE0,0xF8,0x1C,0x06,0x03,0x80,0x18,0x7E,0x00,0xCF,0xF0,0x06,0x7F,0xC0,0x43,0xFE,0x08,0x0F,0xF9,0x00,0x7F,0xE0,0x01,0xF8,0x00,0x0C,0x00, +// '=' +0x3D,0x0D,0x14,0x0E,0x00,0x14, +0x3F,0xFF,0xE6,0x00,0x01,0xE0,0x00,0x1E,0x00,0x01,0xE0,0x00,0x1F,0xFF,0xFE,0xFF,0xFF,0xE6,0x00,0x01,0x60,0x00,0x1E,0x00,0x01,0xE0,0x00,0x1F,0xFF,0xFE,0xFF,0xFF,0xEF,0xFF,0xF8, +// '>' +0x3E,0x0A,0x13,0x13,0x00,0x13, +0x38,0x00,0x0C,0xC0,0x03,0x87,0x00,0x70,0x18,0x0E,0x00,0xE1,0xF8,0x03,0x3F,0xC0,0x19,0xFF,0x01,0x0F,0xF8,0x20,0x3C,0x04,0x0C,0x00,0x86,0x00,0xE3,0x00,0x78,0xC0,0x7C,0x38,0x3E,0x07,0x3E,0x00,0xFF,0x00,0x1F,0x00,0x03,0x80,0x00,0x00, +// '?' +0x3F,0x08,0x11,0x16,0x00,0x11, +0x00,0xF0,0x01,0x82,0x01,0x00,0x81,0x00,0x23,0x00,0x0B,0x00,0x05,0xC0,0x02,0xF8,0xC1,0x3F,0x40,0x87,0xE0,0x80,0xE0,0x40,0x30,0x40,0x18,0x20,0x0F,0xE0,0x07,0x30,0x03,0x04,0x03,0x02,0x01,0x81,0x00,0xE0,0x80,0x78,0x80,0x1F,0x80,0x07,0x80, +// '@' +0x40,0x09,0x16,0x16,0x00,0x16, +0x00,0x3F,0x00,0x06,0x03,0x00,0x23,0xFB,0x01,0x3F,0xFC,0x09,0x81,0xF8,0x48,0x7F,0xE2,0x44,0x13,0xD9,0x23,0x8F,0x41,0x1E,0x1F,0x2C,0x51,0x7C,0xE0,0xC5,0xF3,0x8B,0x06,0xCE,0x28,0x3B,0x38,0xE2,0xEE,0x71,0x4D,0x19,0xE3,0x88,0x73,0xFF,0xD1,0xE7,0x9F,0xA3,0xC7,0xF1,0x07,0xE0,0x38,0x07,0xFF,0x80,0x07,0xF8,0x00, +// 'A' +0x41,0x08,0x19,0x17,0x00,0x19, +0x00,0x0C,0x00,0x00,0x09,0x00,0x00,0x08,0x80,0x00,0x0C,0x20,0x00,0x04,0x08,0x00,0x06,0x04,0x00,0x02,0x01,0x00,0x03,0x00,0xC0,0x03,0x00,0x20,0x01,0x00,0x08,0x01,0x80,0x04,0x00,0x80,0x01,0x00,0xC0,0xC0,0x40,0xC0,0x60,0x20,0x40,0x00,0x08,0x60,0x00,0x06,0x40,0x00,0x00,0xF0,0x00,0x00,0xFE,0x1F,0xE1,0xCF,0xCF,0xF1,0x81,0xF4,0x1B,0x80,0x3E,0x0F,0x00,0x0E,0x06,0x00, +// 'B' +0x42,0x09,0x13,0x15,0x00,0x13, +0x3F,0xFC,0x0C,0x00,0x61,0xC0,0x06,0x38,0x00,0x43,0x00,0x04,0x60,0x60,0x8C,0x0C,0x11,0x81,0x02,0x30,0x00,0x46,0x00,0x08,0xC0,0x00,0x98,0x18,0x13,0x03,0x02,0x60,0x60,0x4C,0x00,0x09,0x80,0x01,0x30,0x00,0x44,0x00,0x11,0x80,0x0E,0x3F,0xFF,0x07,0xFF,0x80, +// 'C' +0x43,0x08,0x16,0x17,0x00,0x16, +0x00,0x7E,0x00,0x06,0x02,0x00,0x60,0x06,0x02,0x00,0x06,0x10,0x00,0x00,0xC0,0x00,0x22,0x00,0x01,0x18,0x0F,0x18,0x40,0x7E,0xC3,0x02,0x3E,0x0C,0x08,0xF0,0x30,0x21,0x80,0xC0,0x83,0x03,0x81,0x1A,0x0E,0x03,0xC4,0x18,0x00,0x08,0x70,0x00,0x19,0xE0,0x00,0x23,0xC0,0x01,0x87,0x80,0x1E,0x0F,0xC1,0xE0,0x0F,0xFE,0x00,0x0F,0xC0,0x00, +// 'D' +0x44,0x09,0x16,0x15,0xFF,0x15, +0x1F,0xFE,0x00,0x80,0x06,0x07,0x00,0x06,0x1C,0x00,0x0C,0x30,0x00,0x10,0xC0,0x00,0x23,0x00,0x00,0x8C,0x0F,0x01,0x30,0x3E,0x04,0xC0,0xF8,0x13,0x03,0xE0,0x4C,0x0F,0x01,0x30,0x00,0x04,0xC0,0x00,0x23,0x00,0x00,0x8C,0x00,0x04,0x30,0x00,0x20,0x80,0x01,0x06,0x00,0x18,0x1F,0xFF,0x80,0x3F,0xF0,0x00, +// 'E' +0x45,0x08,0x11,0x17,0x00,0x11, +0x00,0x01,0x0F,0xFF,0x48,0x00,0x2C,0x00,0x17,0x00,0x0B,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x1A,0x98,0x01,0x8C,0x00,0x86,0x00,0x43,0x00,0x31,0x80,0xD4,0xC0,0x02,0x60,0x01,0x30,0x00,0x98,0x00,0x4C,0x00,0x24,0x00,0x17,0xFF,0xEB,0xFF,0xF8,0x00,0x08, +// 'F' +0x46,0x08,0x11,0x16,0xFF,0x10, +0x00,0x01,0x0F,0xFF,0x48,0x00,0x2E,0x00,0x17,0x00,0x09,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x3A,0x98,0x01,0x8C,0x00,0xC6,0x00,0x43,0x00,0x21,0x81,0xD0,0xC0,0xF0,0x60,0x70,0x30,0x20,0x18,0x10,0x08,0x02,0x0C,0x01,0x07,0xFF,0x03,0xFF,0x00, +// 'G' +0x47,0x08,0x16,0x17,0x00,0x16, +0x00,0x7F,0x00,0x06,0x03,0x00,0x60,0x03,0x82,0x00,0x02,0x10,0x00,0x08,0xC0,0x00,0xC2,0x00,0x06,0x18,0x07,0x30,0x40,0x3C,0x83,0x01,0x3C,0x0C,0x04,0xFF,0xB0,0x14,0x02,0xC0,0x78,0x1B,0x80,0xE0,0x4E,0x01,0x81,0x18,0x00,0x04,0x70,0x00,0x11,0xE0,0x00,0x43,0xC0,0x01,0x07,0x80,0x18,0x0F,0x81,0xC0,0x1F,0xFC,0x00,0x0F,0xC0,0x00, +// 'H' +0x48,0x09,0x16,0x15,0x00,0x16, +0x3F,0xC7,0xF9,0x00,0xE0,0x1E,0x03,0xC0,0x98,0x0B,0x02,0x60,0x2C,0x09,0x80,0xF0,0x26,0x00,0x00,0x98,0x00,0x02,0x60,0x00,0x09,0x80,0x00,0x26,0x00,0x00,0x98,0x00,0x02,0x60,0x3C,0x09,0x80,0xF0,0x26,0x02,0xC0,0x98,0x0B,0x02,0x60,0x2C,0x09,0x00,0x20,0x1F,0xFF,0xFF,0xFF,0xF7,0xFC,0x00,0x00,0x00, +// 'I' +0x49,0x09,0x0B,0x15,0x00,0x0B, +0x3F,0xC8,0x07,0x81,0x70,0x26,0x04,0xC0,0x98,0x13,0x02,0x60,0x4C,0x09,0x81,0x30,0x26,0x04,0xC0,0x98,0x13,0x02,0x60,0x48,0x07,0x00,0xFF,0xEF,0xF8, +// 'J' +0x4A,0x09,0x0F,0x16,0x00,0x0F, +0x03,0xFC,0x08,0x04,0x38,0x08,0x70,0x10,0x60,0x20,0xC0,0x41,0x80,0x83,0x01,0x06,0x02,0x0C,0x04,0x18,0x08,0x30,0x10,0xA0,0x23,0x00,0x44,0x00,0x98,0x01,0x20,0x00,0xC0,0x09,0x00,0x27,0xC1,0x8F,0xFE,0x0F,0xF0,0x00, +// 'K' +0x4B,0x08,0x17,0x18,0x00,0x17, +0x00,0x01,0x80,0x7F,0xEE,0x81,0x80,0x38,0x87,0x81,0xE0,0x8F,0x03,0x80,0x86,0x06,0x00,0x8C,0x08,0x07,0x18,0x00,0x1C,0x30,0x00,0x70,0x60,0x01,0x00,0xC0,0x02,0x01,0x80,0x02,0x03,0x00,0x04,0x06,0x04,0x04,0x0C,0x0C,0x04,0x18,0x18,0x06,0x30,0x38,0x00,0x60,0x78,0x09,0x00,0x30,0x26,0x00,0x71,0x8F,0xFF,0xE4,0x0F,0xFC,0xD0,0x00,0x01,0xC0,0x00,0x00,0x00, +// 'L' +0x4C,0x09,0x11,0x16,0x00,0x11, +0x3F,0xF0,0x30,0x04,0x1C,0x06,0x0E,0x02,0x03,0x01,0x01,0x80,0x80,0xC0,0x40,0x60,0x20,0x30,0x10,0x18,0x08,0x0C,0x04,0xC6,0x03,0xD3,0x00,0x09,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x00,0xB0,0x00,0x58,0x00,0x2F,0xFF,0xD7,0xFF,0xF0,0x00,0x30, +// 'M' +0x4D,0x09,0x20,0x16,0x00,0x1F, +0x00,0xF8,0x1F,0x00,0x01,0x84,0x30,0x80,0x01,0x82,0x61,0x00,0x01,0x82,0x40,0x00,0x01,0x81,0xC0,0x80,0x01,0x01,0x80,0x80,0x03,0x00,0x80,0x40,0x02,0x00,0x00,0x40,0x06,0x00,0x00,0x20,0x06,0x00,0x00,0x20,0x04,0x00,0x00,0x10,0x0C,0x00,0x00,0x10,0x08,0x00,0x00,0x10,0x18,0x08,0x08,0x08,0x10,0x18,0x18,0x08,0x30,0x1C,0x1C,0x04,0x60,0x3C,0x3C,0x02,0x70,0x2E,0x2E,0x06,0x7C,0x27,0x4E,0x3C,0x3F,0x27,0xC6,0xF0,0x07,0xC3,0x8F,0x80,0x01,0x83,0x06,0x00, +// 'N' +0x4E,0x09,0x17,0x15,0x00,0x17, +0x3F,0x07,0xFC,0xC1,0x10,0x07,0xC1,0x78,0x1F,0x81,0xF0,0x23,0x01,0xE0,0x46,0x01,0xC0,0x8C,0x01,0x81,0x18,0x01,0x02,0x30,0x00,0x04,0x60,0x00,0x08,0xC0,0x00,0x11,0x80,0x00,0x23,0x02,0x00,0x46,0x06,0x00,0x8C,0x0E,0x01,0x18,0x1E,0x02,0x30,0x3F,0x04,0x60,0x3F,0x0B,0x00,0x2F,0x17,0xFF,0x8F,0xEF,0xFE,0x0F,0x80, +// 'O' +0x4F,0x08,0x17,0x17,0x00,0x17, +0x00,0x7E,0x00,0x03,0x01,0x80,0x18,0x00,0x80,0x40,0x00,0x81,0x00,0x00,0x86,0x00,0x00,0x08,0x00,0x01,0x30,0x00,0x02,0x40,0x3C,0x03,0x80,0xFC,0x07,0x02,0x3C,0x0E,0x04,0x38,0x1C,0x08,0x30,0x3C,0x08,0x40,0x78,0x0F,0x01,0x30,0x00,0x02,0x70,0x00,0x08,0xF0,0x00,0x10,0xF0,0x00,0xC0,0xF0,0x03,0x00,0xFC,0x1C,0x00,0xFF,0xE0,0x00,0x3F,0x00,0x00, +// 'P' +0x50,0x09,0x13,0x15,0x00,0x13, +0x3F,0xFC,0x08,0x00,0x43,0x80,0x06,0x70,0x00,0x46,0x00,0x04,0xC0,0x00,0x18,0x00,0x0B,0x01,0x81,0x60,0x30,0x2C,0x04,0x05,0x80,0x00,0x30,0x00,0x26,0x00,0x08,0xC0,0x02,0x18,0x01,0x83,0x01,0xE0,0x60,0x30,0x08,0x04,0x03,0x00,0x80,0x7F,0xE0,0x07,0xF8,0x00, +// 'Q' +0x51,0x09,0x17,0x1C,0x00,0x17, +0x00,0x7E,0x00,0x03,0x01,0x00,0x18,0x00,0x80,0x40,0x00,0x81,0x00,0x00,0x86,0x00,0x01,0x08,0x00,0x01,0x30,0x1E,0x02,0x40,0x7E,0x03,0x81,0x1E,0x07,0x02,0x1C,0x0E,0x04,0x18,0x1C,0x04,0x20,0x3C,0x07,0x80,0x78,0x00,0x01,0x30,0x00,0x02,0x70,0x00,0x04,0xF0,0x00,0x10,0xF0,0x00,0x70,0xF0,0x00,0x00,0xFC,0x00,0x40,0xFF,0x81,0x00,0x3F,0x84,0x00,0x0F,0x10,0x00,0x06,0x40,0x00,0x0F,0x00,0x00,0x1C,0x00,0x00,0x00,0x00, +// 'R' +0x52,0x09,0x18,0x17,0x00,0x18, +0x3F,0xFE,0x00,0x60,0x01,0x80,0x70,0x00,0x40,0x70,0x00,0x20,0x30,0x00,0x20,0x30,0x00,0x10,0x30,0x10,0x10,0x30,0x18,0x10,0x30,0x10,0x10,0x30,0x00,0x10,0x30,0x00,0x20,0x30,0x00,0x20,0x30,0x00,0x10,0x30,0x00,0x1E,0x30,0x00,0x0A,0x30,0x18,0x06,0x30,0x1C,0x0C,0x20,0x0E,0x18,0x7F,0xFF,0x30,0x7F,0xE7,0x20,0x00,0x03,0xC0,0x00,0x03,0x80,0x00,0x03,0x00, +// 'S' +0x53,0x07,0x11,0x19,0x00,0x11, +0x00,0x06,0x00,0x02,0x80,0x1E,0x40,0x10,0x20,0x30,0x10,0x30,0x08,0x10,0x04,0x18,0x01,0x08,0x00,0x8C,0x0F,0x46,0x07,0xA3,0x01,0xE1,0xC0,0xE1,0xE0,0x41,0x70,0x21,0x80,0x10,0xC0,0x08,0x70,0x08,0x38,0x04,0x0C,0x04,0x06,0x04,0x03,0x3C,0x01,0xFC,0x00,0xF0,0x00,0x30,0x00,0x00, +// 'T' +0x54,0x08,0x12,0x16,0x01,0x13, +0x30,0x01,0x93,0xFF,0xDC,0x00,0x07,0x00,0x01,0xC0,0x00,0x70,0x00,0x1C,0x00,0x07,0x00,0x01,0xCC,0x07,0x7F,0x01,0xFF,0xC0,0x79,0xB0,0x1C,0x0C,0x04,0x03,0x01,0x00,0xC0,0x40,0x30,0x10,0x0C,0x04,0x03,0x01,0x00,0xC0,0x60,0x60,0x08,0x1F,0xFC,0x07,0xFE,0x00, +// 'U' +0x55,0x09,0x19,0x16,0xFF,0x18, +0x1F,0xF1,0xFF,0x10,0x05,0x00,0x9C,0x07,0xC0,0xCE,0x02,0xE0,0x43,0x01,0x30,0x21,0x80,0x98,0x10,0xC0,0x4C,0x08,0x60,0x26,0x04,0x30,0x13,0x02,0x18,0x09,0x81,0x0C,0x04,0xC0,0x86,0x02,0x60,0x43,0x01,0x30,0x21,0xC0,0x70,0x10,0xE0,0x00,0x10,0x30,0x00,0x08,0x1C,0x00,0x08,0x0F,0x00,0x08,0x03,0xC0,0x08,0x00,0xFC,0x18,0x00,0x1F,0xF8,0x00,0x03,0xF0,0x00, +// 'V' +0x56,0x07,0x19,0x18,0x00,0x19, +0x00,0xC1,0xC0,0x01,0x91,0x98,0x03,0x18,0xC3,0x06,0x04,0x60,0x64,0x02,0x20,0x1F,0x00,0xB0,0x1B,0xC0,0x70,0x0C,0xF0,0x10,0x08,0x38,0x00,0x08,0x0E,0x00,0x04,0x07,0x00,0x04,0x01,0xC0,0x06,0x00,0xE0,0x02,0x00,0x38,0x02,0x00,0x1C,0x01,0x00,0x07,0x01,0x00,0x03,0x81,0x80,0x00,0xE0,0x80,0x00,0x30,0x80,0x00,0x1C,0x40,0x00,0x06,0x40,0x00,0x03,0xE0,0x00,0x00,0xE0,0x00,0x00,0x60,0x00, +// 'W' +0x57,0x07,0x20,0x18,0x00,0x20, +0x00,0x60,0x03,0x80,0x01,0x90,0x06,0x60,0x0E,0x10,0xC6,0x1C,0x38,0x11,0xA6,0x07,0x60,0x11,0x26,0x01,0x78,0x12,0x1C,0x06,0x78,0x0E,0x1C,0x04,0x3C,0x0C,0x08,0x08,0x1C,0x00,0x08,0x10,0x0C,0x00,0x00,0x10,0x0E,0x00,0x00,0x20,0x06,0x00,0x00,0x20,0x07,0x00,0x00,0x40,0x03,0x00,0x00,0x40,0x03,0x80,0x00,0x80,0x03,0x80,0x00,0x80,0x01,0xC0,0x81,0x00,0x01,0xC1,0xC0,0x00,0x00,0xE1,0xC2,0x00,0x00,0xE2,0xE4,0x00,0x00,0x74,0xE4,0x00,0x00,0x7C,0x78,0x00,0x00,0x38,0x78,0x00,0x00,0x30,0x30,0x00, +// 'X' +0x58,0x05,0x19,0x1D,0x00,0x18, +0x00,0x01,0x80,0x00,0x01,0x20,0x00,0x61,0x8C,0x00,0x48,0xC3,0x00,0x44,0x40,0x60,0x43,0x60,0x10,0xC0,0xE0,0x18,0xC0,0x30,0x1C,0xC0,0x00,0x18,0x7C,0x00,0x08,0x1F,0x00,0x08,0x03,0x80,0x08,0x00,0xE0,0x04,0x00,0x38,0x02,0x00,0x0C,0x01,0x80,0x04,0x00,0x60,0x06,0x00,0x10,0x02,0x00,0x07,0x03,0x00,0x00,0x41,0x01,0x00,0x63,0x00,0xC0,0x62,0x00,0xF0,0x63,0x80,0x7C,0x61,0xF0,0x4E,0x60,0x7C,0x63,0xE0,0x0F,0x91,0xE0,0x01,0xF0,0x40,0x00,0x70,0x00,0x00,0x00,0x00,0x00, +// 'Y' +0x59,0x06,0x19,0x1B,0x00,0x19, +0x00,0x00,0x60,0x00,0xC0,0x48,0x00,0x80,0x62,0x00,0x88,0x20,0xC0,0x82,0x30,0x11,0x80,0xB0,0x01,0x80,0x30,0x0D,0x80,0x00,0x0C,0xF8,0x00,0x08,0x3E,0x00,0x04,0x07,0x80,0x04,0x01,0xE0,0x04,0x00,0x78,0x04,0x00,0x1C,0x02,0x00,0x0C,0x02,0x00,0x04,0x02,0x00,0x04,0x02,0x00,0x06,0x01,0x00,0x0E,0x01,0x00,0x0C,0x01,0x00,0x07,0x01,0x00,0x03,0xC0,0x80,0x01,0xF0,0x80,0x00,0x3E,0x00,0x00,0x0F,0xC0,0x00,0x03,0xE0,0x00,0x00,0x40,0x00,0x00, +// 'Z' +0x5A,0x08,0x13,0x17,0xFF,0x12, +0x0C,0x00,0x02,0xFF,0xFC,0xC0,0x01,0x98,0x00,0x23,0x00,0x04,0x60,0x01,0x0C,0x00,0x21,0x80,0x08,0x37,0x01,0x07,0xE0,0x60,0xF8,0x08,0x03,0x01,0x00,0x40,0x70,0x18,0x0D,0x02,0x00,0x20,0xC0,0x04,0x18,0x00,0x86,0x00,0x10,0xC0,0x02,0x10,0x00,0x47,0xFF,0xE8,0xFF,0xFE,0x00,0x01,0x80, +// '[' +0x5B,0x04,0x0B,0x20,0x00,0x0B, +0x00,0x40,0x19,0xFD,0x60,0x2C,0x05,0x80,0xB0,0x16,0x0E,0xC1,0xD8,0x33,0x04,0x60,0x8C,0x11,0x82,0x30,0x46,0x08,0xC1,0x18,0x23,0x04,0x60,0x8C,0x11,0x82,0xB0,0x76,0x02,0xC0,0x58,0x0B,0x01,0x60,0x2F,0xFD,0xFF,0x80,0x60,0x00, +// '\' +0x5C,0x09,0x11,0x18,0x00,0x11, +0x3F,0x00,0x30,0x40,0x38,0x10,0x1E,0x08,0x0F,0x04,0x03,0x81,0x01,0xE0,0x80,0x70,0x20,0x3C,0x10,0x1E,0x08,0x07,0x02,0x03,0xC1,0x00,0xE0,0x40,0x78,0x20,0x3C,0x08,0x0F,0x04,0x07,0x82,0x01,0xC0,0x80,0xF0,0x40,0x38,0x10,0x1E,0x08,0x0F,0xFC,0x03,0xFC,0x01,0xFC, +// ']' +0x5D,0x04,0x0C,0x1F,0xFF,0x0B, +0x30,0x02,0xFF,0x60,0x16,0x01,0x60,0x16,0x01,0x60,0x16,0xC1,0x7C,0x16,0xC1,0x6C,0x10,0xC1,0x0C,0x10,0xC1,0x0C,0x10,0xC1,0x0C,0x10,0xC1,0x0C,0x10,0xC1,0x0C,0x13,0xC1,0x6C,0x16,0x01,0x60,0x16,0x01,0x60,0x16,0x01,0x7F,0xE7,0xFE,0x60,0x00, +// '^' +0x5E,0x1E,0x00,0x00,0x00,0x09, + +// '_' +0x5F,0x20,0x10,0x04,0x00,0x10, +0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +// '`' +0x60,0x00,0x0A,0x09,0x00,0x0B, +0x00,0x06,0x02,0x61,0x8C,0x60,0xBF,0x17,0xFC,0x3E,0x03,0x00, +// 'a' +0x61,0x08,0x19,0x17,0x00,0x19, +0x00,0x0C,0x00,0x00,0x09,0x00,0x00,0x08,0x80,0x00,0x0C,0x20,0x00,0x04,0x08,0x00,0x06,0x04,0x00,0x02,0x01,0x00,0x03,0x00,0xC0,0x03,0x00,0x20,0x01,0x00,0x08,0x01,0x80,0x04,0x00,0x80,0x01,0x00,0xC0,0xC0,0x40,0xC0,0x60,0x20,0x40,0x00,0x08,0x60,0x00,0x06,0x40,0x00,0x00,0xF0,0x00,0x00,0xFE,0x1F,0xE1,0xCF,0xCF,0xF1,0x81,0xF4,0x1B,0x80,0x3E,0x0F,0x00,0x0E,0x06,0x00, +// 'b' +0x62,0x09,0x13,0x15,0x00,0x13, +0x3F,0xFC,0x0C,0x00,0x61,0xC0,0x06,0x38,0x00,0x43,0x00,0x04,0x60,0x60,0x8C,0x0C,0x11,0x81,0x02,0x30,0x00,0x46,0x00,0x08,0xC0,0x00,0x98,0x18,0x13,0x03,0x02,0x60,0x60,0x4C,0x00,0x09,0x80,0x01,0x30,0x00,0x44,0x00,0x11,0x80,0x0E,0x3F,0xFF,0x07,0xFF,0x80, +// 'c' +0x63,0x08,0x16,0x17,0x00,0x16, +0x00,0x7E,0x00,0x06,0x02,0x00,0x60,0x06,0x02,0x00,0x06,0x10,0x00,0x00,0xC0,0x00,0x22,0x00,0x01,0x18,0x0F,0x18,0x40,0x7E,0xC3,0x02,0x3E,0x0C,0x08,0xF0,0x30,0x21,0x80,0xC0,0x83,0x03,0x81,0x1A,0x0E,0x03,0xC4,0x18,0x00,0x08,0x70,0x00,0x19,0xE0,0x00,0x23,0xC0,0x01,0x87,0x80,0x1E,0x0F,0xC1,0xE0,0x0F,0xFE,0x00,0x0F,0xC0,0x00, +// 'd' +0x64,0x09,0x16,0x15,0xFF,0x15, +0x1F,0xFE,0x00,0x80,0x06,0x07,0x00,0x06,0x1C,0x00,0x0C,0x30,0x00,0x10,0xC0,0x00,0x23,0x00,0x00,0x8C,0x0F,0x01,0x30,0x3E,0x04,0xC0,0xF8,0x13,0x03,0xE0,0x4C,0x0F,0x01,0x30,0x00,0x04,0xC0,0x00,0x23,0x00,0x00,0x8C,0x00,0x04,0x30,0x00,0x20,0x80,0x01,0x06,0x00,0x18,0x1F,0xFF,0x80,0x3F,0xF0,0x00, +// 'e' +0x65,0x08,0x11,0x17,0x00,0x11, +0x00,0x01,0x0F,0xFF,0x48,0x00,0x2C,0x00,0x17,0x00,0x0B,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x1A,0x98,0x01,0x8C,0x00,0x86,0x00,0x43,0x00,0x31,0x80,0xD4,0xC0,0x02,0x60,0x01,0x30,0x00,0x98,0x00,0x4C,0x00,0x24,0x00,0x17,0xFF,0xEB,0xFF,0xF8,0x00,0x08, +// 'f' +0x66,0x08,0x11,0x16,0xFF,0x10, +0x00,0x01,0x0F,0xFF,0x48,0x00,0x2E,0x00,0x17,0x00,0x09,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x3A,0x98,0x01,0x8C,0x00,0xC6,0x00,0x43,0x00,0x21,0x81,0xD0,0xC0,0xF0,0x60,0x70,0x30,0x20,0x18,0x10,0x08,0x02,0x0C,0x01,0x07,0xFF,0x03,0xFF,0x00, +// 'g' +0x67,0x08,0x16,0x17,0x00,0x16, +0x00,0x7F,0x00,0x06,0x03,0x00,0x60,0x03,0x82,0x00,0x02,0x10,0x00,0x08,0xC0,0x00,0xC2,0x00,0x06,0x18,0x07,0x30,0x40,0x3C,0x83,0x01,0x3C,0x0C,0x04,0xFF,0xB0,0x14,0x02,0xC0,0x78,0x1B,0x80,0xE0,0x4E,0x01,0x81,0x18,0x00,0x04,0x70,0x00,0x11,0xE0,0x00,0x43,0xC0,0x01,0x07,0x80,0x18,0x0F,0x81,0xC0,0x1F,0xFC,0x00,0x0F,0xC0,0x00, +// 'h' +0x68,0x09,0x16,0x15,0x00,0x16, +0x3F,0xC7,0xF9,0x00,0xE0,0x1E,0x03,0xC0,0x98,0x0B,0x02,0x60,0x2C,0x09,0x80,0xF0,0x26,0x00,0x00,0x98,0x00,0x02,0x60,0x00,0x09,0x80,0x00,0x26,0x00,0x00,0x98,0x00,0x02,0x60,0x3C,0x09,0x80,0xF0,0x26,0x02,0xC0,0x98,0x0B,0x02,0x60,0x2C,0x09,0x00,0x20,0x1F,0xFF,0xFF,0xFF,0xF7,0xFC,0x00,0x00,0x00, +// 'i' +0x69,0x09,0x0B,0x15,0x00,0x0B, +0x3F,0xC8,0x07,0x81,0x70,0x26,0x04,0xC0,0x98,0x13,0x02,0x60,0x4C,0x09,0x81,0x30,0x26,0x04,0xC0,0x98,0x13,0x02,0x60,0x48,0x07,0x00,0xFF,0xEF,0xF8, +// 'j' +0x6A,0x09,0x0F,0x16,0x00,0x0F, +0x03,0xFC,0x08,0x04,0x38,0x08,0x70,0x10,0x60,0x20,0xC0,0x41,0x80,0x83,0x01,0x06,0x02,0x0C,0x04,0x18,0x08,0x30,0x10,0xA0,0x23,0x00,0x44,0x00,0x98,0x01,0x20,0x00,0xC0,0x09,0x00,0x27,0xC1,0x8F,0xFE,0x0F,0xF0,0x00, +// 'k' +0x6B,0x08,0x17,0x18,0x00,0x17, +0x00,0x01,0x80,0x7F,0xEE,0x81,0x80,0x38,0x87,0x81,0xE0,0x8F,0x03,0x80,0x86,0x06,0x00,0x8C,0x08,0x07,0x18,0x00,0x1C,0x30,0x00,0x70,0x60,0x01,0x00,0xC0,0x02,0x01,0x80,0x02,0x03,0x00,0x04,0x06,0x04,0x04,0x0C,0x0C,0x04,0x18,0x18,0x06,0x30,0x38,0x00,0x60,0x78,0x09,0x00,0x30,0x26,0x00,0x71,0x8F,0xFF,0xE4,0x0F,0xFC,0xD0,0x00,0x01,0xC0,0x00,0x00,0x00, +// 'l' +0x6C,0x09,0x11,0x16,0x00,0x11, +0x3F,0xF0,0x30,0x04,0x1C,0x06,0x0E,0x02,0x03,0x01,0x01,0x80,0x80,0xC0,0x40,0x60,0x20,0x30,0x10,0x18,0x08,0x0C,0x04,0xC6,0x03,0xD3,0x00,0x09,0x80,0x04,0xC0,0x02,0x60,0x01,0x30,0x00,0xB0,0x00,0x58,0x00,0x2F,0xFF,0xD7,0xFF,0xF0,0x00,0x30, +// 'm' +0x6D,0x09,0x20,0x16,0x00,0x1F, +0x00,0xF8,0x1F,0x00,0x01,0x84,0x30,0x80,0x01,0x82,0x61,0x00,0x01,0x82,0x40,0x00,0x01,0x81,0xC0,0x80,0x01,0x01,0x80,0x80,0x03,0x00,0x80,0x40,0x02,0x00,0x00,0x40,0x06,0x00,0x00,0x20,0x06,0x00,0x00,0x20,0x04,0x00,0x00,0x10,0x0C,0x00,0x00,0x10,0x08,0x00,0x00,0x10,0x18,0x08,0x08,0x08,0x10,0x18,0x18,0x08,0x30,0x1C,0x1C,0x04,0x60,0x3C,0x3C,0x02,0x70,0x2E,0x2E,0x06,0x7C,0x27,0x4E,0x3C,0x3F,0x27,0xC6,0xF0,0x07,0xC3,0x8F,0x80,0x01,0x83,0x06,0x00, +// 'n' +0x6E,0x09,0x17,0x15,0x00,0x17, +0x3F,0x07,0xFC,0xC1,0x10,0x07,0xC1,0x78,0x1F,0x81,0xF0,0x23,0x01,0xE0,0x46,0x01,0xC0,0x8C,0x01,0x81,0x18,0x01,0x02,0x30,0x00,0x04,0x60,0x00,0x08,0xC0,0x00,0x11,0x80,0x00,0x23,0x02,0x00,0x46,0x06,0x00,0x8C,0x0E,0x01,0x18,0x1E,0x02,0x30,0x3F,0x04,0x60,0x3F,0x0B,0x00,0x2F,0x17,0xFF,0x8F,0xEF,0xFE,0x0F,0x80, +// 'o' +0x6F,0x08,0x17,0x17,0x00,0x17, +0x00,0x7E,0x00,0x03,0x01,0x80,0x18,0x00,0x80,0x40,0x00,0x81,0x00,0x00,0x86,0x00,0x00,0x08,0x00,0x01,0x30,0x00,0x02,0x40,0x3C,0x03,0x80,0xFC,0x07,0x02,0x3C,0x0E,0x04,0x38,0x1C,0x08,0x30,0x3C,0x08,0x40,0x78,0x0F,0x01,0x30,0x00,0x02,0x70,0x00,0x08,0xF0,0x00,0x10,0xF0,0x00,0xC0,0xF0,0x03,0x00,0xFC,0x1C,0x00,0xFF,0xE0,0x00,0x3F,0x00,0x00, +// 'p' +0x70,0x09,0x13,0x15,0x00,0x13, +0x3F,0xFC,0x08,0x00,0x43,0x80,0x06,0x70,0x00,0x46,0x00,0x04,0xC0,0x00,0x18,0x00,0x0B,0x01,0x81,0x60,0x30,0x2C,0x04,0x05,0x80,0x00,0x30,0x00,0x26,0x00,0x08,0xC0,0x02,0x18,0x01,0x83,0x01,0xE0,0x60,0x30,0x08,0x04,0x03,0x00,0x80,0x7F,0xE0,0x07,0xF8,0x00, +// 'q' +0x71,0x09,0x17,0x1C,0x00,0x17, +0x00,0x7E,0x00,0x03,0x01,0x00,0x18,0x00,0x80,0x40,0x00,0x81,0x00,0x00,0x86,0x00,0x01,0x08,0x00,0x01,0x30,0x1E,0x02,0x40,0x7E,0x03,0x81,0x1E,0x07,0x02,0x1C,0x0E,0x04,0x18,0x1C,0x04,0x20,0x3C,0x07,0x80,0x78,0x00,0x01,0x30,0x00,0x02,0x70,0x00,0x04,0xF0,0x00,0x10,0xF0,0x00,0x70,0xF0,0x00,0x00,0xFC,0x00,0x40,0xFF,0x81,0x00,0x3F,0x84,0x00,0x0F,0x10,0x00,0x06,0x40,0x00,0x0F,0x00,0x00,0x1C,0x00,0x00,0x00,0x00, +// 'r' +0x72,0x09,0x18,0x17,0x00,0x18, +0x3F,0xFE,0x00,0x60,0x01,0x80,0x70,0x00,0x40,0x70,0x00,0x20,0x30,0x00,0x20,0x30,0x00,0x10,0x30,0x10,0x10,0x30,0x18,0x10,0x30,0x10,0x10,0x30,0x00,0x10,0x30,0x00,0x20,0x30,0x00,0x20,0x30,0x00,0x10,0x30,0x00,0x1E,0x30,0x00,0x0A,0x30,0x18,0x06,0x30,0x1C,0x0C,0x20,0x0E,0x18,0x7F,0xFF,0x30,0x7F,0xE7,0x20,0x00,0x03,0xC0,0x00,0x03,0x80,0x00,0x03,0x00, +// 's' +0x73,0x07,0x11,0x19,0x00,0x11, +0x00,0x06,0x00,0x02,0x80,0x1E,0x40,0x10,0x20,0x30,0x10,0x30,0x08,0x10,0x04,0x18,0x01,0x08,0x00,0x8C,0x0F,0x46,0x07,0xA3,0x01,0xE1,0xC0,0xE1,0xE0,0x41,0x70,0x21,0x80,0x10,0xC0,0x08,0x70,0x08,0x38,0x04,0x0C,0x04,0x06,0x04,0x03,0x3C,0x01,0xFC,0x00,0xF0,0x00,0x30,0x00,0x00, +// 't' +0x74,0x08,0x12,0x16,0x01,0x13, +0x30,0x01,0x93,0xFF,0xDC,0x00,0x07,0x00,0x01,0xC0,0x00,0x70,0x00,0x1C,0x00,0x07,0x00,0x01,0xCC,0x07,0x7F,0x01,0xFF,0xC0,0x79,0xB0,0x1C,0x0C,0x04,0x03,0x01,0x00,0xC0,0x40,0x30,0x10,0x0C,0x04,0x03,0x01,0x00,0xC0,0x60,0x60,0x08,0x1F,0xFC,0x07,0xFE,0x00, +// 'u' +0x75,0x09,0x19,0x16,0xFF,0x18, +0x1F,0xF1,0xFF,0x10,0x05,0x00,0x9C,0x07,0xC0,0xCE,0x02,0xE0,0x43,0x01,0x30,0x21,0x80,0x98,0x10,0xC0,0x4C,0x08,0x60,0x26,0x04,0x30,0x13,0x02,0x18,0x09,0x81,0x0C,0x04,0xC0,0x86,0x02,0x60,0x43,0x01,0x30,0x21,0xC0,0x70,0x10,0xE0,0x00,0x10,0x30,0x00,0x08,0x1C,0x00,0x08,0x0F,0x00,0x08,0x03,0xC0,0x08,0x00,0xFC,0x18,0x00,0x1F,0xF8,0x00,0x03,0xF0,0x00, +// 'v' +0x76,0x07,0x19,0x18,0x00,0x19, +0x00,0xC1,0xC0,0x01,0x91,0x98,0x03,0x18,0xC3,0x06,0x04,0x60,0x64,0x02,0x20,0x1F,0x00,0xB0,0x1B,0xC0,0x70,0x0C,0xF0,0x10,0x08,0x38,0x00,0x08,0x0E,0x00,0x04,0x07,0x00,0x04,0x01,0xC0,0x06,0x00,0xE0,0x02,0x00,0x38,0x02,0x00,0x1C,0x01,0x00,0x07,0x01,0x00,0x03,0x81,0x80,0x00,0xE0,0x80,0x00,0x30,0x80,0x00,0x1C,0x40,0x00,0x06,0x40,0x00,0x03,0xE0,0x00,0x00,0xE0,0x00,0x00,0x60,0x00, +// 'w' +0x77,0x07,0x20,0x18,0x00,0x20, +0x00,0x60,0x03,0x80,0x01,0x90,0x06,0x60,0x0E,0x10,0xC6,0x1C,0x38,0x11,0xA6,0x07,0x60,0x11,0x26,0x01,0x78,0x12,0x1C,0x06,0x78,0x0E,0x1C,0x04,0x3C,0x0C,0x08,0x08,0x1C,0x00,0x08,0x10,0x0C,0x00,0x00,0x10,0x0E,0x00,0x00,0x20,0x06,0x00,0x00,0x20,0x07,0x00,0x00,0x40,0x03,0x00,0x00,0x40,0x03,0x80,0x00,0x80,0x03,0x80,0x00,0x80,0x01,0xC0,0x81,0x00,0x01,0xC1,0xC0,0x00,0x00,0xE1,0xC2,0x00,0x00,0xE2,0xE4,0x00,0x00,0x74,0xE4,0x00,0x00,0x7C,0x78,0x00,0x00,0x38,0x78,0x00,0x00,0x30,0x30,0x00, +// 'x' +0x78,0x05,0x19,0x1D,0x00,0x18, +0x00,0x01,0x80,0x00,0x01,0x20,0x00,0x61,0x8C,0x00,0x48,0xC3,0x00,0x44,0x40,0x60,0x43,0x60,0x10,0xC0,0xE0,0x18,0xC0,0x30,0x1C,0xC0,0x00,0x18,0x7C,0x00,0x08,0x1F,0x00,0x08,0x03,0x80,0x08,0x00,0xE0,0x04,0x00,0x38,0x02,0x00,0x0C,0x01,0x80,0x04,0x00,0x60,0x06,0x00,0x10,0x02,0x00,0x07,0x03,0x00,0x00,0x41,0x01,0x00,0x63,0x00,0xC0,0x62,0x00,0xF0,0x63,0x80,0x7C,0x61,0xF0,0x4E,0x60,0x7C,0x63,0xE0,0x0F,0x91,0xE0,0x01,0xF0,0x40,0x00,0x70,0x00,0x00,0x00,0x00,0x00, +// 'y' +0x79,0x06,0x19,0x1B,0x00,0x19, +0x00,0x00,0x60,0x00,0xC0,0x48,0x00,0x80,0x62,0x00,0x88,0x20,0xC0,0x82,0x30,0x11,0x80,0xB0,0x01,0x80,0x30,0x0D,0x80,0x00,0x0C,0xF8,0x00,0x08,0x3E,0x00,0x04,0x07,0x80,0x04,0x01,0xE0,0x04,0x00,0x78,0x04,0x00,0x1C,0x02,0x00,0x0C,0x02,0x00,0x04,0x02,0x00,0x04,0x02,0x00,0x06,0x01,0x00,0x0E,0x01,0x00,0x0C,0x01,0x00,0x07,0x01,0x00,0x03,0xC0,0x80,0x01,0xF0,0x80,0x00,0x3E,0x00,0x00,0x0F,0xC0,0x00,0x03,0xE0,0x00,0x00,0x40,0x00,0x00, +// 'z' +0x7A,0x08,0x13,0x17,0xFF,0x12, +0x0C,0x00,0x02,0xFF,0xFC,0xC0,0x01,0x98,0x00,0x23,0x00,0x04,0x60,0x01,0x0C,0x00,0x21,0x80,0x08,0x37,0x01,0x07,0xE0,0x60,0xF8,0x08,0x03,0x01,0x00,0x40,0x70,0x18,0x0D,0x02,0x00,0x20,0xC0,0x04,0x18,0x00,0x86,0x00,0x10,0xC0,0x02,0x10,0x00,0x47,0xFF,0xE8,0xFF,0xFE,0x00,0x01,0x80, +// '{' +0x7B,0x05,0x0D,0x1F,0x01,0x0E, +0x00,0x10,0x0E,0x81,0x04,0x10,0x21,0x81,0x08,0x08,0xC0,0x46,0x0E,0x30,0x71,0x83,0x0C,0x10,0x60,0x84,0x04,0x60,0x23,0x02,0x18,0x08,0xC0,0x47,0x82,0x3C,0x10,0x60,0x83,0x05,0x18,0x38,0xC0,0x46,0x02,0x38,0x11,0xC0,0x87,0x84,0x1F,0xE0,0x7F,0x00,0x30,0x00,0x00, +// '|' +0x7C,0x1E,0x00,0x00,0x00,0x09, + +// '}' +0x7D,0x04,0x0E,0x1F,0x00,0x0F, +0x30,0x00,0xBC,0x06,0x0C,0x18,0x08,0x60,0x21,0x80,0x46,0x01,0x1F,0x04,0x7C,0x11,0xB0,0x44,0xC1,0x03,0x04,0x0C,0x0C,0x30,0x10,0xE0,0x43,0x81,0x04,0x04,0x30,0x70,0xC1,0x03,0x04,0x0C,0x10,0xF0,0x46,0xC1,0x18,0x04,0x60,0x11,0x80,0x86,0x04,0x18,0x60,0x7F,0x81,0xF8,0x06,0x00,0x00, +// '~' +0x7E,0x1E,0x00,0x00,0x00,0x09, + + +// Terminator +0xFF +}; diff --git a/components/mkspiffs/.travis.yml b/components/mkspiffs/.travis.yml new file mode 100644 index 0000000..5c77d15 --- /dev/null +++ b/components/mkspiffs/.travis.yml @@ -0,0 +1,24 @@ +language: cpp +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 +script: + - export CXX="g++-4.8" CC="gcc-4.8" + - make dist +notifications: + email: + recipients: + - ivan@esp8266.com + on_success: change + on_failure: always +deploy: + provider: releases + api_key: + secure: k/DDoCfLXIct8TcGjekKm5CAoTL6Wy6LXhI74Ssgc8VSbjJQ1crO2J4V5HnQw7QplgAXwqjkoAUkzEJiz34wqBaAv0w9o8+5jPwCP6rMQ/VEvbn1MPI52KIFbYqKxmXe5J24B00BttbGc773ldXPnvmv+qPWWcXDTpcosni2laBt3z8bxCGVwkQ7nuQUaAelzs21wQuhjmKQ6F1dgNN5XhdJ5qgFYYM8cwiigvcqIaCwLMcrOs7gj22TS242pzp38etWhfxUbqUKejWgOH4qYeU3Tf5gsu4WV0otbqXYMvV18gVSoAiMnodDfYJUNHlfelCAEqebvECrIvvE8D0syuVYz6fh2/BrxZ6HeiYj1CXILghOjPUZZZ7/chKglWnA1vL+6Uxn5LtyTJ5gbgMYXvKQUXrRVZ3Zd9xrmv/YaMnGq+6BDBkS30aXpSK2X0HvW9/6JyQ9L1sjnKdOzDmagvikHm2rrPXBRMMfYTt4nucgUBWqJqyFe0uGva/n8TG5RzOzdOgRxFx/lF8XudtR4Z4gUBUFpve/meVHVVK82+cxzfN97aFdxvBzyGq18EDjOEpDi7k7mZjXUycvD8UZ7o12sxJ0Zr6/9esiFUsaqyE+2Vi6bMbgEHGx7hfWbdfREnpOtjFLH+u5mAPIqOh89a/UJ6SbYm0cON+ewTMUkJ8= + file: mkspiffs-$TRAVIS_TAG-linux64.tar.gz + on: + repo: igrr/mkspiffs + tags: true diff --git a/components/mkspiffs/Makefile.projbuild b/components/mkspiffs/Makefile.projbuild new file mode 100644 index 0000000..0f805ad --- /dev/null +++ b/components/mkspiffs/Makefile.projbuild @@ -0,0 +1,14 @@ +MKSPIFFS_COMPONENT_PATH := $(COMPONENT_PATH) +MKSPIFFS_BUILD_DIR=$(abspath $(MKSPIFFS_COMPONENT_PATH)/mkspiffs) + +# Custom recursive make for mkspiffs sub-project +MKSPIFFS_MAKE=+$(MAKE) -C $(MKSPIFFS_COMPONENT_PATH)/src + +.PHONY: mkspiffs clean + +mkspiffs: $(SDKCONFIG_MAKEFILE) + $(MKSPIFFS_MAKE) all + +clean: $(SDKCONFIG_MAKEFILE) + $(MKSPIFFS_MAKE) clean + diff --git a/components/mkspiffs/component.mk b/components/mkspiffs/component.mk new file mode 100644 index 0000000..12f5e46 --- /dev/null +++ b/components/mkspiffs/component.mk @@ -0,0 +1,6 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := +COMPONENT_ADD_INCLUDEDIRS := diff --git a/components/mkspiffs/src/Makefile.original b/components/mkspiffs/src/Makefile.original new file mode 100644 index 0000000..ce2261c --- /dev/null +++ b/components/mkspiffs/src/Makefile.original @@ -0,0 +1,93 @@ +CFLAGS ?= -std=gnu99 -Os -Wall +CXXFLAGS ?= -std=gnu++11 -Os -Wall + +ifeq ($(OS),Windows_NT) + TARGET_OS := WINDOWS + DIST_SUFFIX := windows + ARCHIVE_CMD := 7z a + ARCHIVE_EXTENSION := zip + TARGET := mkspiffs.exe + TARGET_CFLAGS := -mno-ms-bitfields + TARGET_LDFLAGS := -Wl,-static -static-libgcc + +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Linux) + TARGET_OS := LINUX + UNAME_P := $(shell uname -p) + ifeq ($(UNAME_P),x86_64) + DIST_SUFFIX := linux64 + endif + ifneq ($(filter %86,$(UNAME_P)),) + DIST_SUFFIX := linux32 + endif + endif + ifeq ($(UNAME_S),Darwin) + TARGET_OS := OSX + DIST_SUFFIX := osx + CC=clang + CXX=clang++ + TARGET_CFLAGS = -mmacosx-version-min=10.7 -arch i386 -arch x86_64 + TARGET_CXXFLAGS = -mmacosx-version-min=10.7 -arch i386 -arch x86_64 -stdlib=libc++ + TARGET_LDFLAGS = -arch i386 -arch x86_64 -stdlib=libc++ + endif + ARCHIVE_CMD := tar czf + ARCHIVE_EXTENSION := tar.gz + TARGET := mkspiffs +endif + +VERSION ?= $(shell git describe --always) + +OBJ := main.o \ + spiffs/spiffs_cache.o \ + spiffs/spiffs_check.o \ + spiffs/spiffs_gc.o \ + spiffs/spiffs_hydrogen.o \ + spiffs/spiffs_nucleus.o \ + +INCLUDES := -Itclap -Ispiffs -I. + +CFLAGS += $(TARGET_CFLAGS) +CXXFLAGS += $(TARGET_CXXFLAGS) +LDFLAGS += $(TARGET_LDFLAGS) + +CPPFLAGS += $(INCLUDES) -D$(TARGET_OS) -DVERSION=\"$(VERSION)\" -D__NO_INLINE__ + +DIST_NAME := mkspiffs-$(VERSION)-$(DIST_SUFFIX) +DIST_DIR := $(DIST_NAME) +DIST_ARCHIVE := $(DIST_NAME).$(ARCHIVE_EXTENSION) + +.PHONY: all clean dist + +all: $(TARGET) + +dist: test $(DIST_ARCHIVE) + +$(DIST_ARCHIVE): $(TARGET) $(DIST_DIR) + cp $(TARGET) $(DIST_DIR)/ + $(ARCHIVE_CMD) $(DIST_ARCHIVE) $(DIST_DIR) + +$(TARGET): $(OBJ) + $(CXX) $^ -o $@ $(LDFLAGS) + strip $(TARGET) + +$(DIST_DIR): + @mkdir -p $@ + +clean: + @rm -f *.o + @rm -f spiffs/*.o + @rm -f $(TARGET) + +SPIFFS_TEST_FS_CONFIG := -s 0x100000 -p 512 -b 0x2000 + +test: $(TARGET) + ls -1 spiffs > out.list0 + ./mkspiffs -c spiffs $(SPIFFS_TEST_FS_CONFIG) out.spiffs | sort | sed s/^\\/// > out.list1 + ./mkspiffs -u spiffs_u $(SPIFFS_TEST_FS_CONFIG) out.spiffs | sort | sed s/^\\/// > out.list_u + ./mkspiffs -l $(SPIFFS_TEST_FS_CONFIG) out.spiffs | cut -f 2 | sort | sed s/^\\/// > out.list2 + diff --strip-trailing-cr out.list0 out.list1 + diff --strip-trailing-cr out.list0 out.list2 + diff spiffs spiffs_u + rm -f out.{list0,list1,list2,list_u,spiffs} + rm -R spiffs_u diff --git a/components/mkspiffs/src/README.md b/components/mkspiffs/src/README.md new file mode 100644 index 0000000..6d9a275 --- /dev/null +++ b/components/mkspiffs/src/README.md @@ -0,0 +1,80 @@ +# mkspiffs +Tool to build and unpack [SPIFFS](https://github.com/pellepl/spiffs) images. + + +## Usage + +``` + + mkspiffs {-c |-u |-l|-i} [-d <0-5>] [-b ] + [-p ] [-s ] [--] [--version] [-h] + + + +Where: + + -c , --create + (OR required) create spiffs image from a directory + -- OR -- + -u , --unpack + (OR required) unpack spiffs image to a directory + -- OR -- + -l, --list + (OR required) list files in spiffs image + -- OR -- + -i, --visualize + (OR required) visualize spiffs image + + + -d <0-5>, --debug <0-5> + Debug level. 0 means no debug output. + + -b , --block + fs block size, in bytes + + -p , --page + fs page size, in bytes + + -s , --size + fs image size, in bytes + + --, --ignore_rest + Ignores the rest of the labeled arguments following this flag. + + --version + Displays version information and exits. + + -h, --help + Displays usage information and exits. + + + (required) spiffs image file + + +``` +## Build + +You need gcc (≥4.8) or clang(≥600.0.57), and make. On Windows, use MinGW. + +Run: +```bash +$ make dist +``` + +### Build status + +Linux | Windows +------|------- + [![Linux build status](http://img.shields.io/travis/igrr/mkspiffs.svg)](https://travis-ci.org/igrr/mkspiffs) | [![Windows build status](http://img.shields.io/appveyor/ci/igrr/mkspiffs.svg)](https://ci.appveyor.com/project/igrr/mkspiffs) + + +## License + +MIT + +## To do + +- [ ] Add more debug output and print SPIFFS debug output +- [ ] Error handling +- [ ] Determine the image size automatically when opening a file +- [ ] Code cleanup diff --git a/components/mkspiffs/src/appveyor.yml b/components/mkspiffs/src/appveyor.yml new file mode 100644 index 0000000..42f99d4 --- /dev/null +++ b/components/mkspiffs/src/appveyor.yml @@ -0,0 +1,28 @@ +version: 0.0.{build} + +platform: + - x86 + +skip_commits: + message: /\[ci skip\]/ + +matrix: + fast_finish: true + +build_script: + - SET PATH=C:\MinGW\bin;C:\MinGW\msys\1.0\bin;%PATH% + - make dist + +artifacts: + - path: '*.zip' + +deploy: + release: $(PRODUCT_VERSION) + provider: GitHub + auth_token: + secure: 'PGg5fnoBpP1Omzr6f3KIYDiD8J30rretQjSl/MITRpzvSCmN88kM6VDMz1TBGZTA' + artifact: /.*\.zip/ + draft: true + prerelease: false + on: + appveyor_repo_tag: true diff --git a/components/mkspiffs/src/main.cpp b/components/mkspiffs/src/main.cpp new file mode 100644 index 0000000..1a7a774 --- /dev/null +++ b/components/mkspiffs/src/main.cpp @@ -0,0 +1,646 @@ +// +// main.cpp +// make_spiffs +// +// Created by Ivan Grokhotkov on 13/05/15. +// Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. +// +#define TCLAP_SETBASE_ZERO 1 +#define VERSION "0.3.6" + +#include +#include "spiffs/spiffs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tclap/CmdLine.h" +#include "tclap/UnlabeledValueArg.h" + +static std::vector s_flashmem; + +static std::string s_dirName; +static std::string s_imageName; +static int s_imageSize; +static int s_pageSize; +static int s_blockSize; + +typedef struct { + time_t mtime; + time_t ctime; + time_t atime; + uint8_t spare[SPIFFS_OBJ_META_LEN - (sizeof(time_t)*3)]; +} spiffs_metadata_t; + +enum Action { ACTION_NONE, ACTION_PACK, ACTION_UNPACK, ACTION_LIST, ACTION_VISUALIZE }; +static Action s_action = ACTION_NONE; + +static spiffs s_fs; + +static std::vector s_spiffsWorkBuf; +static std::vector s_spiffsFds; +static std::vector s_spiffsCache; + + +static s32_t api_spiffs_read(u32_t addr, u32_t size, u8_t *dst){ + memcpy(dst, &s_flashmem[0] + addr, size); + return SPIFFS_OK; +} + +static s32_t api_spiffs_write(u32_t addr, u32_t size, u8_t *src){ + memcpy(&s_flashmem[0] + addr, src, size); + return SPIFFS_OK; +} + +static s32_t api_spiffs_erase(u32_t addr, u32_t size){ + memset(&s_flashmem[0] + addr, 0xff, size); + return SPIFFS_OK; +} + + +int g_debugLevel = 0; + + +//implementation + +int spiffsTryMount(){ + spiffs_config cfg = {0}; + + cfg.phys_addr = 0x0000; + cfg.phys_size = (u32_t) s_flashmem.size(); + + cfg.phys_erase_block = s_blockSize; + cfg.log_block_size = s_blockSize; + cfg.log_page_size = s_pageSize; + + cfg.hal_read_f = api_spiffs_read; + cfg.hal_write_f = api_spiffs_write; + cfg.hal_erase_f = api_spiffs_erase; + + const int maxOpenFiles = 4; + s_spiffsWorkBuf.resize(s_pageSize * 2); + s_spiffsFds.resize(32 * maxOpenFiles); + s_spiffsCache.resize((32 + s_pageSize) * maxOpenFiles); + + return SPIFFS_mount(&s_fs, &cfg, + &s_spiffsWorkBuf[0], + &s_spiffsFds[0], s_spiffsFds.size(), + &s_spiffsCache[0], s_spiffsCache.size(), + NULL); +} + +bool spiffsMount(){ + if(SPIFFS_mounted(&s_fs)) + return true; + int res = spiffsTryMount(); + return (res == SPIFFS_OK); +} + +bool spiffsFormat(){ + spiffsMount(); + SPIFFS_unmount(&s_fs); + int formated = SPIFFS_format(&s_fs); + if(formated != SPIFFS_OK) + return false; + return (spiffsTryMount() == SPIFFS_OK); +} + +void spiffsUnmount(){ + if(SPIFFS_mounted(&s_fs)) + SPIFFS_unmount(&s_fs); +} + +// WHITECAT BEGIN +int addDir(const char* name) { + spiffs_metadata_t meta; + + std::string fileName = name; + fileName += "/."; + + std::cout << fileName << std::endl; + + spiffs_file dst = SPIFFS_open(&s_fs, fileName.c_str(), SPIFFS_CREAT, 0); + if (dst < 0) { + std::cerr << "SPIFFS_write error(" << s_fs.err_code << "): "; + + if (s_fs.err_code == SPIFFS_ERR_FULL) { + std::cerr << "File system is full." << std::endl; + } else { + std::cerr << "unknown"; + } + std::cerr << std::endl; + + SPIFFS_close(&s_fs, dst); + return 1; + } + + SPIFFS_close(&s_fs, dst); + if (strlen(name) > 0) { + // Get the system time to file timestamps + meta.atime = time(NULL); + meta.ctime = meta.atime; + meta.mtime = meta.atime; + SPIFFS_update_meta(&s_fs, fileName.c_str(), &meta); + } + + return 0; +} +// WHITECAT END + +int addFile(char* name, const char* path) { + spiffs_metadata_t meta; + + FILE* src = fopen(path, "rb"); + if (!src) { + std::cerr << "error: failed to open " << path << " for reading" << std::endl; + return 1; + } + + spiffs_file dst = SPIFFS_open(&s_fs, name, SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_RDWR, 0); + + // read file size + fseek(src, 0, SEEK_END); + size_t size = ftell(src); + fseek(src, 0, SEEK_SET); + + if (g_debugLevel > 0) { + std::cout << "file size: " << size << std::endl; + } + + size_t left = size; + uint8_t data_byte; + while (left > 0){ + if (1 != fread(&data_byte, 1, 1, src)) { + std::cerr << "fread error!" << std::endl; + + fclose(src); + SPIFFS_close(&s_fs, dst); + return 1; + } + int res = SPIFFS_write(&s_fs, dst, &data_byte, 1); + if (res < 0) { + std::cerr << "SPIFFS_write error(" << s_fs.err_code << "): "; + + if (s_fs.err_code == SPIFFS_ERR_FULL) { + std::cerr << "File system is full." << std::endl; + } else { + std::cerr << "unknown"; + } + std::cerr << std::endl; + + if (g_debugLevel > 0) { + std::cout << "data left: " << left << std::endl; + } + + fclose(src); + SPIFFS_close(&s_fs, dst); + return 1; + } + left -= 1; + } + + SPIFFS_close(&s_fs, dst); + + // Get the system time to file timestamps + meta.atime = time(NULL); + meta.ctime = meta.atime; + meta.mtime = meta.atime; + SPIFFS_update_meta(&s_fs, name, &meta); + + fclose(src); + + return 0; +} + +int addFiles(const char* dirname, const char* subPath) { + DIR *dir; + struct dirent *ent; + bool error = false; + std::string dirPath = dirname; + dirPath += subPath; + + // Open directory + if ((dir = opendir (dirPath.c_str())) != NULL) { + + // Read files from directory. + while ((ent = readdir (dir)) != NULL) { + // Ignore dir itself. + if (ent->d_name[0] == '.') + continue; + + std::string fullpath = dirPath; + fullpath += ent->d_name; + struct stat path_stat; + stat (fullpath.c_str(), &path_stat); + + if (!S_ISREG(path_stat.st_mode)) { + // Check if path is a directory. + if (S_ISDIR(path_stat.st_mode)) { + // Prepare new sub path. + std::string newSubPath = subPath; + newSubPath += ent->d_name; + + // WHITECAT BEGIN + addDir(newSubPath.c_str()); + // WHITECAT END + + newSubPath += "/"; + + if (addFiles(dirname, newSubPath.c_str()) != 0) + { + std::cerr << "Error for adding content from " << ent->d_name << "!" << std::endl; + } + + continue; + } + else + { + std::cerr << "skipping " << ent->d_name << std::endl; + continue; + } + } + + // Filepath with dirname as root folder. + std::string filepath = subPath; + filepath += ent->d_name; + std::cout << filepath << std::endl; + + // Add File to image. + if (addFile((char*)filepath.c_str(), fullpath.c_str()) != 0) { + std::cerr << "error adding file!" << std::endl; + error = true; + if (g_debugLevel > 0) { + std::cout << std::endl; + } + break; + } + } // end while + closedir (dir); + } else { + std::cerr << "warning: can't read source directory" << std::endl; + return 1; + } + + return (error) ? 1 : 0; +} + +void listFiles() { + spiffs_DIR dir; + spiffs_dirent ent; + + SPIFFS_opendir(&s_fs, 0, &dir); + spiffs_dirent* it; + while (true) { + it = SPIFFS_readdir(&dir, &ent); + if (!it) + break; + + std::cout << it->size << '\t' << it->name << std::endl; + } + SPIFFS_closedir(&dir); +} + +/** + * @brief Check if directory exists. + * @param path Directory path. + * @return True if exists otherwise false. + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + */ +bool dirExists(const char* path) { + DIR *d = opendir(path); + + if (d) { + closedir(d); + return true; + } + + return false; +} + +/** + * @brief Create directory if it not exists. + * @param path Directory path. + * @return True or false. + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + */ +bool dirCreate(const char* path) { + // Check if directory also exists. + if (dirExists(path)) { + return false; + } + + // platform stuff... +#if defined(_WIN32) + if (_mkdir(path) != 0) { +#else + if (mkdir(path, S_IRWXU | S_IXGRP | S_IRGRP | S_IROTH | S_IXOTH) != 0) { +#endif + std::cerr << "Can not create directory!!!" << std::endl; + return false; + } + + return true; +} + +/** + * @brief Unpack file from file system. + * @param spiffsFile SPIFFS dir entry pointer. + * @param destPath Destination file path path. + * @return True or false. + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + */ +bool unpackFile(spiffs_dirent *spiffsFile, const char *destPath) { + u8_t buffer[spiffsFile->size]; + std::string filename = (const char*)(spiffsFile->name); + + // Open file from spiffs file system. + spiffs_file src = SPIFFS_open(&s_fs, (char *)(filename.c_str()), SPIFFS_RDONLY, 0); + + // read content into buffer + SPIFFS_read(&s_fs, src, buffer, spiffsFile->size); + + // Close spiffs file. + SPIFFS_close(&s_fs, src); + + // Open file. + FILE* dst = fopen(destPath, "wb"); + + // Write content into file. + fwrite(buffer, sizeof(u8_t), sizeof(buffer), dst); + + // Close file. + fclose(dst); + + + return true; +} + +/** + * @brief Unpack files from file system. + * @param sDest Directory path as std::string. + * @return True or false. + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + * + * todo: Do unpack stuff for directories. + */ +bool unpackFiles(std::string sDest) { + spiffs_DIR dir; + spiffs_dirent ent; + + // Add "./" to path if is not given. + if (sDest.find("./") == std::string::npos && sDest.find("/") == std::string::npos) { + sDest = "./" + sDest; + } + + // Check if directory exists. If it does not then try to create it with permissions 755. + if (! dirExists(sDest.c_str())) { + std::cout << "Directory " << sDest << " does not exists. Try to create it." << std::endl; + + // Try to create directory. + if (! dirCreate(sDest.c_str())) { + return false; + } + } + + // Open directory. + SPIFFS_opendir(&s_fs, 0, &dir); + + // Read content from directory. + spiffs_dirent* it = SPIFFS_readdir(&dir, &ent); + while (it) { + // Check if content is a file. + if ((int)(it->type) == 1) { + std::string name = (const char*)(it->name); + std::string sDestFilePath = sDest + name; + size_t pos = name.find_last_of("/"); + + // If file is in sub directory? + if (pos > 0) { + // Subdir path. + std::string path = sDest; + path += name.substr(0, pos); + + // Create subddir if subdir not exists. + if (!dirExists(path.c_str())) { + if (!dirCreate(path.c_str())) { + return false; + } + } + } + + // Unpack file to destination directory. + if (! unpackFile(it, sDestFilePath.c_str()) ) { + std::cout << "Can not unpack " << it->name << "!" << std::endl; + return false; + } + + // Output stuff. + std::cout + << it->name + << '\t' + << " > " << sDestFilePath + << '\t' + << "size: " << it->size << " Bytes" + << std::endl; + } + + // Get next file handle. + it = SPIFFS_readdir(&dir, &ent); + } // end while + + // Close directory. + SPIFFS_closedir(&dir); + + return true; +} + +// Actions + +int actionPack() { + s_flashmem.resize(s_imageSize, 0xff); + + FILE* fdres = fopen(s_imageName.c_str(), "wb"); + if (!fdres) { + std::cerr << "error: failed to open image file" << std::endl; + return 1; + } + + spiffsFormat(); + + // WHITECAT BEGIN + addDir(""); + // WHITECAT END + + int result = addFiles(s_dirName.c_str(), "/"); + spiffsUnmount(); + + fwrite(&s_flashmem[0], 4, s_flashmem.size()/4, fdres); + fclose(fdres); + + return result; +} + +/** + * @brief Unpack action. + * @return 0 success, 1 error + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + */ +int actionUnpack(void) { + int ret = 0; + s_flashmem.resize(s_imageSize, 0xff); + + // open spiffs image + FILE* fdsrc = fopen(s_imageName.c_str(), "rb"); + if (!fdsrc) { + std::cerr << "error: failed to open image file" << std::endl; + return 1; + } + + // read content into s_flashmem + ret = fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc); + + // close fiel handle + fclose(fdsrc); + + // mount file system + spiffsMount(); + + // unpack files + if (! unpackFiles(s_dirName)) { + ret = 1; + } + + // unmount file system + spiffsUnmount(); + + return ret; +} + + +int actionList() { + int ret = 0; + s_flashmem.resize(s_imageSize, 0xff); + + FILE* fdsrc = fopen(s_imageName.c_str(), "rb"); + if (!fdsrc) { + std::cerr << "error: failed to open image file" << std::endl; + return 1; + } + + ret = fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc); + fclose(fdsrc); + spiffsMount(); + listFiles(); + spiffsUnmount(); + + ret = 0; + return ret; +} + +int actionVisualize() { + int ret = 0; + s_flashmem.resize(s_imageSize, 0xff); + + FILE* fdsrc = fopen(s_imageName.c_str(), "rb"); + if (!fdsrc) { + std::cerr << "error: failed to open image file" << std::endl; + return 1; + } + + ret = fread(&s_flashmem[0], 4, s_flashmem.size()/4, fdsrc); + fclose(fdsrc); + + spiffsMount(); + //SPIFFS_vis(&s_fs); + uint32_t total, used; + SPIFFS_info(&s_fs, &total, &used); + std::cout << "total: " << total << std::endl << "used: " << used << std::endl; + spiffsUnmount(); + + ret = 0; + return ret; +} + +void processArgs(int argc, const char** argv) { + TCLAP::CmdLine cmd("", ' ', VERSION); + TCLAP::ValueArg packArg( "c", "create", "create spiffs image from a directory", true, "", "pack_dir"); + TCLAP::ValueArg unpackArg( "u", "unpack", "unpack spiffs image to a directory", true, "", "dest_dir"); + TCLAP::SwitchArg listArg( "l", "list", "list files in spiffs image", false); + TCLAP::SwitchArg visualizeArg( "i", "visualize", "visualize spiffs image", false); + TCLAP::UnlabeledValueArg outNameArg( "image_file", "spiffs image file", true, "", "image_file" ); + TCLAP::ValueArg imageSizeArg( "s", "size", "fs image size, in bytes", false, 0x10000, "number" ); + TCLAP::ValueArg pageSizeArg( "p", "page", "fs page size, in bytes", false, 256, "number" ); + TCLAP::ValueArg blockSizeArg( "b", "block", "fs block size, in bytes", false, 4096, "number" ); + TCLAP::ValueArg debugArg( "d", "debug", "Debug level. 0 means no debug output.", false, 0, "0-5" ); + + cmd.add( imageSizeArg ); + cmd.add( pageSizeArg ); + cmd.add( blockSizeArg ); + cmd.add(debugArg); + std::vector args = {&packArg, &unpackArg, &listArg, &visualizeArg}; + cmd.xorAdd( args ); + cmd.add( outNameArg ); + cmd.parse( argc, argv ); + + if (debugArg.getValue() > 0) { + std::cout << "Debug output enabled" << std::endl; + g_debugLevel = debugArg.getValue(); + } + + if (packArg.isSet()) { + s_dirName = packArg.getValue(); + s_action = ACTION_PACK; + } else if (unpackArg.isSet()) { + s_dirName = unpackArg.getValue(); + s_action = ACTION_UNPACK; + } else if (listArg.isSet()) { + s_action = ACTION_LIST; + } else if (visualizeArg.isSet()) { + s_action = ACTION_VISUALIZE; + } + + s_imageName = outNameArg.getValue(); + s_imageSize = imageSizeArg.getValue(); + s_pageSize = pageSizeArg.getValue(); + s_blockSize = blockSizeArg.getValue(); +} + +int main(int argc, const char * argv[]) { + + try { + processArgs(argc, argv); + } catch(...) { + std::cerr << "Invalid arguments" << std::endl; + return 1; + } + + switch (s_action) { + case ACTION_PACK: + return actionPack(); + break; + case ACTION_UNPACK: + return actionUnpack(); + break; + case ACTION_LIST: + return actionList(); + break; + case ACTION_VISUALIZE: + return actionVisualize(); + break; + default: + break; + } + + return 1; +} diff --git a/components/mkspiffs/src/mkspiffs b/components/mkspiffs/src/mkspiffs new file mode 100755 index 0000000000000000000000000000000000000000..cb8fb9a56af28b5d2476c26b4ba2fb7bb6605813 GIT binary patch literal 145776 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ø?0>NPTpn}F z=i50Iyh=ym+~Pf$k671a*(Y*-t;JH}D!CS@vz8Wj%ja;gy;?0buFe|CBf)n}Dqyalr7p+We^_FU%^ z4L`caC!Y10n3giWqJzws2${3RapqK!V(g?Z(GD*ggRl1EEeK#lxZ9z)8FQuao`Dm~ zitfm92rI)^n&NU_^+Hqp&3DFo0r9GF6z(teM{w}}rocgnz0Fkg!7##8PlaRhEzbRr zNkC?q4;bPM^EN~L)V#(J%Y5c}hFIk@KS3!ur{d!vM03qGhB(`Qvm{w+h@bmRl=EpH z9T-Fb2UnO&d}6wJj_5!O0|-%ejpn0%ai;lezgX%sSNO$EKJ#3^_?^$hD=AOHWmYQp6-@Gzle&jcSFEdTNOYlq6|B5-_Po{ZZ zfmm38f*Zp3Q}I)T_-om}Sj@&BDl_aD#7!~rrqXHVeLm4;Vr~9BH+}!=1G>@RsLJ*G z`-WcPLuGx}`OKSq{=0nUM?U{m#z5>T32+6YArmo}&oTdOh^u|(V}`iYI8nC2zEh`Q zVeIoiYlsa#^HDUT&%}E|U;5f)^gf4yf7ranCr&jl_KC$ly8R^=V9(KrtTvbX#B3A$ zWGlB14;lpOq2|GmRmpnzc=KvQoB+WESMa{uXa2<}9$|TDuL9K>`z$ljf98sSxW;E* z9T0!l96Psk&F^Xq{4 zv)|kj5O4TR9O?eRZ>|fV6I4kCjX*dG1!+~!HOC{2n&$=Z1?FG*#YGV30AAQIZ}W>S z@;>7;|Lqr78s=a9;#R}_gJ0ZbnD_X_Ck9cT=SNqI>*(zmG`yGjabtnEnH&6~$Naz$ z7x>JVAz?mqy&)d=5&WXhyxT9{MD6_IQ?!gXv@A%B+h2nocbK5|1iDAAskc#x+VDY43{-RKPW|;po#IOA3Z~XWnUh{=Q@oK<) zwotqqF#lL6z6zM@3&o|T`ADI-(KPQb#Hnx-9|Tx$nrjNh)dj@omj&j_zzfU=3dE)Y z^RETss($7T{lvO{<{$crxBHnN^b?m3FmD|!ZW~}OA1u}mFuxuoo*H0o86;jCU~U>D zJ{Vx)fZgJO=C22dD+Zb?28p)^66dRes-cdK{T)N^X~Vp#0K?E+Tp+GNLz?0izxkRe z*7*0t{d@k0e293##MT(vcIXd~^f(g086<#><~&OX2MtHmMDrvxlq5av$fe-E9s|P^ zFQEHK*O?!h;&Y$*zA0`rIxwC4&7T#BoBbvn$G`TQ|1-q~zxjy?VZl)OQ^0(rfLiU2 z0`XSB#P5b}3s4zXnZHvCqyI>Al$fA0+q>R;)kj^s!AEKk_aDizfpNOmiGyJ{`pmn0 zJ{0DUKJgmGfQ8c0r;Zzj_c-?bbwANNbVWZ>kc<0?dr;B>@fiBqC*JT~;Cls`8^#sr z0$J9T&?}}`958`j8}L6-fDf^ncNK^Y0rSQJalJ|Sqy1FLjv9kGnihV=Rd8-+X)h$uBN69`ynGgr%)*3<^}g@2mYpdf0kdpx^W(g^0r4X&5gTh(8(T z`U3G!i9quIQ6TQ~Q|#aT=352gV}BI)Zw35c7Kp{B`A&gYX;SRNrn$7Ac-Ab#{YSEF z*YD%o{bir|y3co!VPbB$*4W+AF1!5*1_=h@ZPcJB5jy{MgIfD^^B;Rtr~lb6`h4aZ zzj)AR-sl%E8Rlj~Too{v7gKBeeJF|jV?)JX1Lo~R#YX{i=};Q}pY1K)!T$rrr>6Pp zFmYjl`PeXVO@VpqFmY#rxpbI#p}_ojs94s|eDy%_i+<+K`-r>ynHTRPp6h4+Ynb@D zpZV~C;*$R6d;5r=_cx#4NBp6`xn>{nS%3591I2j*%x8y-YX+EWhKqX#nAZwQx$ z_7N`*Fz+Z77Y#J8C=}}k(y)4Fpt-J4EGab4N2)>QL;H#=2bnkRD{dcTUbL_HV37IY zaB=5g^SuMay20j6`-v9@o9FE(-W_bdyRWz*Xg;^Ecs^)8dw_T&XkN5GjwPBO?k6tV z&3tY@@w?s3-|dGFX_$8(AU@sA{BVD9Ns;;7{$h2J`Mdq`!3^`p{l(JVORhUW{B{p> z>mK6WJ)STY9U$%~R^l>n<^;U-@vszLSQ@3|&BN0hKJls#heb$cA=5*A=6K(*5kkxu z+>Chw3w-=3kw*N^Zx^Om(O@MKvvHE0T@W;tpT!f zmIuU#(C~h72Q|{0h9CSsmi!j_%{wp``6-|O>P9+bum68-MD_aq`o0CeZ-MVy;QJQ% zz6HK-f$v-3`xbyV>loDvua>K@n4fjql-~wB8rKm~g`CdeX1PJ3F#2D|bjvyZ<~(%m zOm{EGug^obkm(W}zc3HoTBbYubXArM8&#T!r^8P+{3T6)t2x zW8tWx>xK(dIOkjyE?KC;MG+OAtMlb>C5I{&`xKo2aL)G#PyT^#nE$QkD~>ITx1axm zp8SX1K0+p#-Kf&_a=3;=g<{X1>1Hv07Kd9sbhDYRf$>EgKIO?b!E`e@ekq5`ZWZ!s zYEt2{l`7Q#FXVKk*D2)K>s9zX^Z&S=DQ{O{<7yS&ze0sCGu>tmhjTv_b9g9+M{+ot z!(%zDUj-yPL|7}x)$9mw&VM#WG6 zudDOvTUVH1TbFI{mSyn^xm+|Hbw;r1r*W*C*si;@w@zQ&rHNBP> z{k$W2`KEez8IN}@CEoDRLY1S&C-UjyNBClR97?Y_peu&wN<8=TB zjlAaFsmiDK?p8Bn+SjA2p9;iK4*TN_30)fhVwif?<(3~B1T;XwR{jU@v!?qhqw0s& zlF&AOE@yrje%9sO%FpL>LH^6nYnbnM9kkz}+QrNF9q#YXI4qz&PF#C)cqoTsIjrVz z7Kg1I_HuYWhgWlWD~D@1+`!>W9KOThXB-xc;rux~l*6$cR&zLu!&VM^IXs`kt2w-t z!!;ak;P535-{J5x4hzO|{v004;aCo-n>4iDvUEQi$`&f>6@!(I;0=kRI{Z{=_ehZ{J2iNkj|{EWkba?YQ_LpdDFVKs+} zYUls=?^E(v%q;Z0Rm%EV+tE6QNfRgjsCeXwO+8)dp5k#OWhJFYmG{V}QHQH%O_@K$ z3#g)8D+ULdAG^E_IdQ*8=fne+9&@k}eqjo&zub6%rRUsuKcUCJ8}EE#6A&L|Y((ABd}pAY&p1Xs>+U}Yk3NCf^%gPl711> z>-o8s@#`7iQ?U~<#(%@Op6@Sb{C>tMKf2z>AC>1d#=hNycAXVB}|VqD5}2qC`!%e4#qb#Ud;5rXS|Q`0OK1NU+1Bx z?g-{`?FW!&rMxCJ&)jd#%mu_z#_(9VfEgBmc&>8RGW`XwD*8I+-;{?w&GcWss|crXN1n@g6%BA)dog}B<0pTh z@VP1weT=VX{Bg$D1J6~?7n%O@{)(}DCj@EUWBiH%3V(Z#@1F^ggkNcsj@Ym8;zfrCemsj|TwvVYT9&rw)_!L$c<(a>l*$UcfEd$X z$oQ;Ag)=N}1-=L97jTEq;fQ;Hj}Uu{#H}M_f{`kjc!ue#dB7dQ_}?YY4~}48Eaa#> z>!S+Pb*un>lh{WT@jR}}(+Zr*GjLypY-V1UGCt!#g|j*kE1CbT>{r!t^*ZCvvcK+N z<}(HpKb1%O`B>b=RN%U7Oe%vLnO^(Z=o=$+6&5N!vm50jWJX=I-Y5RG&FXm`<39yX z^*V#wjh8>-GT>xA&j+eN z(C^;tVFZPDTnq(HY2qv(Dxmw{ipgAa;%CL2SBik(Vkwr{V>o|IrV;t=0o4cBHXLrZUDZ!V_l>B z_YS7l{_x{DU-}*+@kw(7|B~_M-4#ANrU3d5H(iec&(&Z51Ww~o`xp08WTu}0JQx2{n0`S*5z_aD={ir+2gSv#Ngh?OxR&YD*DIij@%6x|Jlda% zV$16l=9BobdY0ew#_hWjSD5pEKU9SJaXfvz50@_gP~cRrr><7iQ&k`)GycKv6hPlC zqH7i7+E0I&g2ltkKb29yP_FNDz^VM&kG>z%ed@Tn*$ABI*R#GF&GgAU^fyX8 zD86L9ifLM259FcW9TOSx|GGudp}XZZlJSZ=)iZsQjIQ&6Q+)$l)U*7K32v`u`jTHM zyo?)qH>f<~@3qI;f$whYBeefbrNTSvz^VQ9{ubEEYpKMGgx&|*m+?CZ2cMBVPxWVf z6L88`?-zf}<@pr29M^j*ppkK7xT4qlmw#tI<&1m#>leU@&zb_o;1cHZTi`T)^}dbP z8(%RWy>G7NVFW6o?>S2ubV)oYu0BC;Sq7neu3KYe>FSH0->*@%zc z4<65ao?(31N(IpOHR&3%e{MdfOFSsf-Kyx`X8JQ2Kjk!q%kTB!_8j1puio#zQALZp z7+-pk0_Z!)bPYZrcfLnSJScw1?W4!zag5hgsq}c1*EHOae3p9TGtPLi2dCee%hhi8 z0;lrpa}cw*z6CHKsUP1vR^@j-<9h<9aa8xq5i+7!MT;YV6Q3X7rhrEoznbx2rvj!j zADmx@y^(!w1)i&%!;2Mvz3)Arll~vZ^*+5$`y_CE&!jSVSK>i&$w*a@UXCK2dl()4v6r^jID5!(;l87k!G6^7XD4W?>;f_{>H{ z$81Cwa4OH<`zwUL3rQFKz9Hck_!PcM!QvX`qtA!vc6*HJk6)(L)&j%dFU4br+mG3?^?zmzE&~U zdj5?(e2y7O^~%0308aehYf*8U=AbdE9U6b6@E>y0Ye7%tU(4(Lg^a(=eDpa#R*S+s zT=6k#6d|n}=^8F^b?^Ki$%ChWQ@#nFFBWp5i}TRm2%PGr&kt2H{U?m;bA0D=|DJY) zqSxo7bUWM&oaFEqa}Tg#f0(}eLVOlADqN4RpD@1nbqdEcC$C$W&*wa@<@W~=b3fyYc;XtO zvKLP??q>zCmihdZ@jWIg!cx}TUjnD{ESRSVFJeBY9Hr_t{|5??-}L}VcbSqay{|ru z`7Z!H@zLl1biHnpe3-})(8r*OUY{S-^oN#EzS-A!;6$&_wQ7BGdLH@})x;-zpLzlF zX=+k=yvAAmhViY93K-A$M#&%Z+J_1#Wc+pDwBGUh)7}S8@}tkk6*K*AN2~U{lQZP$ zL>vK}{A0!JAM4Nbvn4%0ID+5i!9QmHe|c3Azr_7$l&W^q=Zi-%uLFSRYUdEsi`y09 zvrK;i=B3?H&V{^x7hwD((376g=Ww+=q?ykicPc`3x4f=md`MC~({D=A^(k-~M;mxv zxJAKYYo2*igjBusd1GCdVZil0vyedyxYWa|N66=e9C{oC2KWS)ZrX{oW3o))&M2R5>+%G1LF3Q86CG{BLJ` zHsjpw;tAl?U;6w2Z!Zb@z2H4Ktur_Rf{_ca1-`qJknew%hkhe)s+T_BTh8?FN&2Ap z;!_1IV*T?u;}?Fd@SB-FfC-x9;Rv?Rs~F!y;{4zUnt)UP>T{Sy9Cvme`lp!xohyJ- zeIvXMt>*+QnSNHI0(AdA%(y-muI1!S;M5N5Jo4-xo4bF90;lrp^R|OI-;uznocdgN z0ppV;pP;yw9h@(6fliTpVCUSfxHfPT^E(6oGx1bwb?cMS7+lI1YSeBycN&u02{y^2uFGySeM z<-65m2fi!$z^{0`YRF-%hrb3+a_gPf`eQ;QIa$Yc(cWC1gMgFV?*Ekn?q)p1__}Jv zA8J!x#{;MK(eEATx}T`|6e#*@nLft!`aO={GX5gtm6t0(^WOvZH}Ut{QB{oV_Ykzb zF_&@g{CEN5`aOb5&iC)Yb(tBG!6$j}VaKTQ==TNmyg3Fq^^1NFXBab^1Dx8kVv=fy z7~^z)i|W;WjRLqE#Q!8c6FGuuSbq|qO@C7$ogbpBmGR1dtLM2Y5Iw+&|IJYa=sI2v zoXWX{$Mr&{{}t2E=~4jwJ|$hxGJavBe6;rI-eFw7Uz1eP;`rlKzAa}cfYq_MiE;h@ z2-K;(9%o#?hokk?+rV|1nN$Yjf2{bwv|OP>Ir0j|^?CVOj6cn|cOU9=#{17xgjk-) z>zw0r=X;gJgQANUC@^p2bq8>2XYaUvh3RJ;tomJkqYo+mm50B-B6q$A0w@07{l{^V z9_zA+V2X>?t_WACalMcQY9ASk?_~kc)t-xi6aT6!6yb0chzA+>o(I@-f|8$vXI?l0 zxRlTTDh4$^6_!hSXh&X`>GGV&^lfYi)N;NtNsmv0c=+E1oXV-+ThMy=56tIvl$Wk` zEVr*P{mRo7zOM?zr;?r@96<^Sf%tp(XV(Fz@gi8?YJ2i6i5H1tuGfCdD}eqWK64rs zpz}S5as574(x>7p8J~W=dWKs@UaNrT>aPcx-n(D<65|i@dRF_N4@LXt;&YtDgTi;X zDv10JB66O|c>H5kj{m4Yv;(Jl>GziOd6G>UzgZE|?`P3f6jpM$ncHWkf<-IvV!n3< zi-42-e7d_TkNhSq2yT(MlTzJ30G!IB--|+b$g2qJM8frZUMDcVM6MsP-?~l#Z!rFI zroWKoMC+v|Bs~*3f_)~ba`v%4H#qKai8~1Swgx!KkA7cJ&kL=LkMzjZCCvXLo@Y@E zdEEh=>ZRYy)bjs0^O@}#FK;ql?pYs=pRD+J?U@MUUjOqt#`Sw}eO#V{YI4ifM2W+n z#^<1pRNX1&0H^Zn_vkdA2lDWFmFd?sstm_-zW-(1drs`6DXL!{ctjCudpiYucTVdJ zE(cEK(eI=BIr0w1_4|(f8Gjadv6BJc|BdNqpQV6j7{|B#a*n%0fEP23Gx%{H`pbZm z{OI??ph@Mm5;&EgJ`hLOSQUs5B_H^sSbi|g$!i;MT0b`4H9|h?_2XVpyt(wl;Svvu zk2kCI@;5zjJ03XYtKT2x<-M4lhtC~MpCz}9>-R9VoF6ky^`rQg%D0l`XR^c<=KOyx zaB3g@9;_Z;zX49=Ea_F$y4}9Y!$)*R<6R}qiG+yEsSS;dCgPD)BHr4Xio}x9RP2m1 zBwm_|w01;O?VYjCEV?t3@ige_+GWfb2Zv-V+QJdjr;cfuSUaI^)P!VPXk4VZJ=Q$; zqa&%9^67 zcylC`PR6_1YMSfQp-^*sG#N=Jqw#dAra4?6#lLWvIgbM8`gD0D(i-pRh(v4J!_&t{ zB5hqgid`fU>xwpY#3J$5n%SvjeHxMRRHSKsI#$yV2_x6uXh%;h+#4PniKOQzV&S^_ zQIYy_k(pBwsyxPwr_!S!WszC2W>hPJBF1X!(_?$(V@=(3yA`N-NEw#l7=)owk!g{Z zShS;~yE&RB{$nDYit#wvEZ@HF6U)MNQ=1(nM6*#59Tgwd6+1J+d3Gn=B@CBah3TxD zs=H{^XxWmZMn@t&UGc7XIv(waFNn27I=g$R`66B2v3bq0L>i4;x1+j`jZiaC^&3hf z4Uzisk^1t;%!UxRXlUG6WL7&G;+LYE@u^5lJUKnu8KbB2R3zG*j(2y_LnxGr#5<#H zF&%3G^(dXBfD|V{U_B#66B>K21&m3t@SoJ3oLkw`s`;uBQQacT5E`YaCPpF0A~LCV zM&*RsNF>$M6sd`{p%0sS+G=CHu@0oKPuD``;@#9xqB)sPrF&XiOPWPXEE#Kyr_wP9 zY-gmoqq{2x?va-6NLxpDQ?w(}0{KDxdgh7d?#@IzmBq0|Oq`E5}i6)cL z`4HrEa=vIyqLm^oJ)NENk;L&7p`y4*dB8VQbx<_tqinE9TP&ReZB52vIbba_Iiiv= zDI?t-EwNO(q`A8*l};qP6D6epk(OvWstObDYKhH@Bx9{;!LDYj z1T8UYqX?2jI-(2aYmUj7)K921Qh7m=290%echBufD9se<)hL%hqrNejATppC5qS-Pow-PtY4+7>QKRoG>jM2~V$zMDR3e`iYV7WCkZ!%@mQUlc!IZRx=TC zu2IwC2GEnqw514UR9DxB8zK!8Dr>_L(VC8RbW~JKs;#M<7%9VxnPZ?8cpfMVP3&x` zjd#WBqRCV&EVU-o8PxX9O?A+nHuKOp%(WiIq3?omSy_D|)*Nq*W12!~?P7l?+(V;h z#gZxTWz(Q46z&!Ebv09MObt&O~=r` zEwOYo-VquTsm1uk1l1CgQ}Z~?xa}DCm}^TT+()x#M@E&5E(z_po2p7tvdA3WnbNc= zgBpr=!XQe{jJ3j87$cu^Nu+b^VJIZpTaubgjwq$iR1GYfr&LuyniblZ7^kbR4T^Hq z3Ys7at4bvgghJSY_X3^$Y3ezWw9VN3R9c>Gv)l?c- zO5wVONFCWZ^_Z{W$+84^9LullB-z+jQNN*i28%Nh>uMo8rPJL5Wztkp22=SgGQJ?s zH9KPghC`*8W~hqb5Uz{Qw5<}V5Dbv9FhJ~DxumM9Of{n_NT`fhSOawzBwmfxP#N16 z-O0|V6xfD(E!(5GacA_T6Fq4vpJf`mi!u7l_GlWyRu-FAIYt`Cj=FeoA)=;A>=pDR_TSDb( zd5yxjmWD&Jhb(N_H}&a6NB#vseu;sOhS3>9WoDo^AmC$Y5};|>St*?{b%0gV@%8Bz z-7l=1V##Dzx2`R%8&Z*UH&4FOCX17Wr?o|B+=*Qs(IyxzExRV$NM+fzrQa&D59Er+)j=jgw|V66>%W zrE^Gvdnh`zh%F*KQJ{(RLa-f52WnO87RTba9Lw3x>JBL2nLE*>VJt|gzDik*t%`Nj zrD;)DAF*Z$Ic{y5(72kmuI^-PCWgJ9acSIf`*)LajwfyU@lslA<8x!W@G7r-rpzvA zND---fzj9!?~3%KVlcUMy>@Xn(3917GPO*k3eqfsmFxIOle%*?IF*|_*cORUDU9ys zSRH&9>F~_@?wzyu;_*1R!{LBgH{G7>J`+|AOnUMl^twhAsH}O#ZoRmi^sETGYaLP? zDxKY(oD4IeBbF?O;)Na6mBN^SNRh!KDeL2-6EMueS$TRCdRb{_Je7jC1_@KvBx(y= zD=%0&VXaZC+2cjqIx zvI%OhqGICg*%K;jBB7E|THLX8`$ero!C71 zqngul)a-(G^~!2o=?)&H?sl^pBgYB={UzH0t1{`eVJWc}Cf{CBswNoiv7eRo=1^>7 z;o@l9`56YD$L7vNj^b*SS|7>oA(9g6snM3SjE#uSxlm|17+IFp3}Gj(Q7A4llc^XSE{=EVFg(NcMn>f#o` z%Bxf>?2cGhTe=+%qhxo|YiVe$gcc^Rbqso|qoWDtPNcP`3mVO-qrPdEjmP3J3MJgu zMf27UN9~w+s@~ndyC^W(DiQLo7rs!x%C+QD{yQ4knEj@~A1DV1+%#lU)=ZI=qeY6- z*^MC%Tc;D=RlU^3GG{ccYGI2dW;8g|cn;mUYi^lHOR5`t88nfi=Pc2;o6|EJ=Nc1L zCzZhOS>i~ZqnL8&I23Pry1QO#GF}^Eu(GniqPU3)JRR7yF+%WT^vh6$HdYhT&uZ2* zG~RO2gvP=l(b?Tqr#zXkOr_O@qK05h!0Q)niuZo1r`Kn?+kxL+Z<)|8Q7d4hr(`s2m+eSMwkZDVi{2H_a6_;)W zd!pMJl}RfM@`!4Oh(k7Ig|WAwXI$4lSk`u;)}?ZM$$5I$gKq!c>}-o79>~M`ITpW4{gC`gFn}%a~I$ zbgQMfJtEiH-4bubNdVndayg>pzU=lqZLH_nZ9bNvw)s*w3|BM%ZCPQ9`9X zUGRdwWPKy zcC^-2&Ni%=Q8sL9=wv)SU~ddtzjTHs0>6UXSYblTx%CaVWkY4M6<|qVu58V_J8O%; zH}yXxYV&NUa8paY#O@)z%fE~EfzE|&Ii zxywoL-Oa8_H1sOE4zmZ;ePkYa0o|?4<5?f;@*dqn+gg@PzV*Mgcg-iLEPb$x0x2yX zEUwv;sP5cmj2hnGcGQl=5kyS4;ZC<4soBD+u5FW$&^che8!QJ*mU0;jS1(RdhC8*Y zOpr~7sa>0`WV?{aYMq(AvB-+pX1ybICygoh%#=>T_lbENIfcs9diq3ZMOZ3amg`LG zG>z9K&d18^lG?+dEQB>y6V79`sAX3yCD)ZZw-u|KiOm;%7*C#Fq{BW|OIapfDNH;} zG~^nx%{!2Ndo2quYrQ(YLr0>qUq@SD*c)~!CnXl#U9Li~tHv%wD;>OYk1wnEd3Ju) zYKTr#U^asB7a>QC&4TPdOAYC>6yti;(XV{Y8as{ml-QVFI!JE&mZa53d&ZH5X%qGN zK3mrFlsT;X%sRp;k7AK>p`#mssaT%E>mi6O3YqA6FGIJ`uNZ^sm zF$sA<(&3Tbi9sRfrExSbvE3HyrW4+l6l(Ri^KRd0Jx&tk+*7eUO7_&j{(fx=cqLlu zW^3!taszSeecPD+LxA0q zS}?{#ow2vJH<$a*C2q1_N)DaJR-{0AkE}S)jq!3DJFU&#J=z3NXE>=u-+m0jK+Ur; zCRdQO1&8+@V)JkW7Dw_uzBP|Gj<)$y@f|tjcII@_Xfn^k+dDF;d%@bFWucd+$@K_m zmAj`Lqt2A0)v#TZb1myMd}()__QkC|YdU9(twK7Q>Y8Q1&sGIF9QNP7hcL>#b~dR- zmSN=w!CCu+cK$epAlCs}rL^G0qm3=s;aPRs9c@IXyY1N{r{mgMi#fCVhN~^9nWPKR z;oq}%LRmX|4|1||FR!&_O^GPErDU=fznJsjpvgX3VcCVUd1x%DoU~kxf}={#(<Ok2V&vDPR~IY#(|F}=BFN#c$SfaWGL6UkGI z_jtPG-khp_o`gKP+ zW4mYbErXOLK-PVhB>+3b{jOWigIdxQM%UnVIJniay>gCilTc|qH8Tc#IT>qF+iu!2 z%Qt9X&qO-nO*qh!>XGjbx%Je})J^X*<=$qLXYzM^+GPyAv+CGpwB_WRx_8FWk*{Pg z909I*-Z2GZ^(4KAm;_ql^a`3bH!vE=8*h)A9LHDK`nIMl&-CLwk2*$9k)Fe-N=@5d zVQtN82cSnW*z&S7U1HZH|H*VNWz+Wj{P{iHb`I9yD#PHy$!_(Uk(9c?TgS*Q)A}Ventak2h{0d4CjcwRh<#9jI>KU=S-yB=kg7v`gH6kct6~EJPfZ%<$MX8d?>D$z?-a>z_-__ z^uCm5KZvSsmA4*m2kN5~ZVlx&rsP=C8`a-vzIE(p@O-B_K;RTDO=9lc)Ts{~RLyZN z+S1vM0Y%&+bH;a|OK9gVMNgKs=$+w64`!bR@tQie7TS&^<(jG3`gJWOchOp++Gy?W zbd~a?&SrqC%I-H7Nf7B=%tR^lYAeCwxQo-96mnH*8fA_VUXF1hyMsJ^IB{V2oaH-k zP&<{~w>$65M~-l4N*!Lt>Op;f0S8#*=L~SHIZ}hkbp~8V*mc&z#_v_CbK$B6yVw?N zI9M}99@Ctf%ofjF3m~hh>;lDeoNU$#Sgw4?U2jcIG|VxI9IsfGe!EvJUCK(oZmBj1 z=!G7+O4!aC!C7v0h@gYDCe&*pQp7k$jKi<$y(_2cgFh+jWNdpqy&Bia4!Qd7j#Sul zK+o1wv{F**EIrrj?RvGHZ;PX|tlz!)JD%Ko=ue(N?D31ahF(DU2InXpvSPhtwrj8G zp+q$ev>w%;e1K1yKKRf;9~{ws3n*&K_a?vK5RoqJZfq2s*mWkot&dlU%27@n62=r(JKraU+af!!GPhLu)HlI{~I`i*rA65=BrN#4A zNU!S@m0f0@K)(Hle6Y@^GppGfr7DN-tZt6|by`D0o8W6e)}cbGmTTdo2e9Y68IIxk zot4ZU+p=IvLZ$x#U-*dU+o*9JhH-cWdDW1w|8~WU(ru#TS31P7XrTjnI5AH4m9s_N zt!YihG~~zAA-OZBVY9)x&e0QbXcU;*uO|{oqzy-8l}m@d=wVF+#4JQd=K2ma_3}_hKBMJaAQA?eAGyL|YfT?je)jG3Z#` zEAo(Fx+lr6s5RsKud5)URc;b6#_tBQcA{5wa4JJtaC?Giexf%!^)FM<~YmP=zNUNTqPIB2hIUy5TDqUD{Gu3ENw*Ohus z+pd$r{@ek7>VOoR+Yd(V=@tc1lA7O{jy54oCso+4@9^$&ESV4`UES$e3EreV3f~Rk zCwy0}q^T#~(Q*_Pt|jy&r?8Ti`CZ^AWSCB>7~b@EJwy}=jW1Sw>(H&5^LwLUC|*6>j~VOanfCVm<;uR;U$@=4`C@D`kweu?qDfP z8tsfX2+9aQK}pMOsR#^_okic)0|$1hgg)k)8jL!;Gk&x>3hfY4NINAh?wEhVecUItp9u4k3N5zag_gj{;;wR z_4hh;`E@=zoQw3^9O=usU^-mK8B_VGoVxrBiv_r$KGFQX;Pg5y8lot5{yM#`|GAvL zQjv-NN2wbfuBMD}QF=<_O}`WvwF#4nKKzaWT{_g?nO3MOtG=i3BK$ei)9)tGrNf0( zIme~HN2$Xr5$8-_SFVV4sLQKa=w}_?=t2;Xk-znGWb-w!f7Ek(({C!&;4$y=g zda5IC9eoS^rfe&^g3Lr z2Oy{SuoHjuq~EwqrPpEKKu*YkxBh)%`Mw2v~8fX6V?i?Xm&%0BlzfgCqM6=630A$Ye*6(uksq`$)oZ3#W0TOR2ldgY7-v}91 z&gqLi9agLpa!9|oL^+juaGm~O4^FgnIYUnsWqc#&UsNU^#r_DI@u%Bwn1Th3FI{$q kWRUoW%AuGh2*;)K)ewE-I9K{*rK(i?^M0D#Dz^syF9R;png9R* literal 0 HcmV?d00001 diff --git a/components/mkspiffs/src/spiffs/esp_spiffs.c b/components/mkspiffs/src/spiffs/esp_spiffs.c new file mode 100644 index 0000000..088480c --- /dev/null +++ b/components/mkspiffs/src/spiffs/esp_spiffs.c @@ -0,0 +1,138 @@ +/* + * Lua RTOS, SPIFFS low access + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#include + +#include "esp_spiffs.h" +#include "esp_attr.h" + +#include "spiffs.h" + +#include + +s32_t esp32_spi_flash_read(u32_t addr, u32_t size, u8_t *dst) { + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & (u32_t)-4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & (u32_t)-4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *)(((ptrdiff_t)buff + (4 - 1)) & (u32_t)-4); + + if (spi_flash_read(aaddr, (void *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(dst, abuff + (addr - aaddr), size); + + free(buff); + } else { + if (spi_flash_read(addr, (void *)dst, size) != 0) { + return SPIFFS_ERR_INTERNAL; + } + } + + return SPIFFS_OK; +} + +s32_t esp32_spi_flash_write(u32_t addr, u32_t size, const u8_t *src) { + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & -4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & -4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *)(((ptrdiff_t)buff + (4 - 1)) & -4); + + if (spi_flash_read(aaddr, (void *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(abuff + (addr - aaddr), src, size); + + if (spi_flash_write(aaddr, (uint32_t *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + free(buff); + } else { + if (spi_flash_write(addr, (uint32_t *)src, size) != 0) { + return SPIFFS_ERR_INTERNAL; + } + } + + return SPIFFS_OK; +} + +s32_t IRAM_ATTR esp32_spi_flash_erase(u32_t addr, u32_t size) { + if (spi_flash_erase_sector(addr >> 12) != 0) { + return SPIFFS_ERR_INTERNAL; + } + + return SPIFFS_OK; +} diff --git a/components/mkspiffs/src/spiffs/esp_spiffs.h b/components/mkspiffs/src/spiffs/esp_spiffs.h new file mode 100644 index 0000000..c68e652 --- /dev/null +++ b/components/mkspiffs/src/spiffs/esp_spiffs.h @@ -0,0 +1,43 @@ +/* + * Lua RTOS, write syscall implementation + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#ifndef __ESP_SPIFFS_H__ +#define __ESP_SPIFFS_H__ + +#include "spiffs.h" + +s32_t esp32_spi_flash_read(u32_t addr, u32_t size, u8_t *dst); +s32_t esp32_spi_flash_write(u32_t addr, u32_t size, const u8_t *src); +s32_t esp32_spi_flash_erase(u32_t addr, u32_t size); + +#define low_spiffs_read (spiffs_read *)esp32_spi_flash_read +#define low_spiffs_write (spiffs_write *)esp32_spi_flash_write +#define low_spiffs_erase (spiffs_erase *)esp32_spi_flash_erase + +#endif // __ESP_SPIFFS_H__ diff --git a/components/mkspiffs/src/spiffs/spiffs.h b/components/mkspiffs/src/spiffs/spiffs.h new file mode 100644 index 0000000..d87422d --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs.h @@ -0,0 +1,813 @@ +/* + * spiffs.h + * + * Created on: May 26, 2013 + * Author: petera + */ + +#ifndef SPIFFS_H_ +#define SPIFFS_H_ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "spiffs_config.h" + +#define SPIFFS_OK 0 +#define SPIFFS_ERR_NOT_MOUNTED -10000 +#define SPIFFS_ERR_FULL -10001 +#define SPIFFS_ERR_NOT_FOUND -10002 +#define SPIFFS_ERR_END_OF_OBJECT -10003 +#define SPIFFS_ERR_DELETED -10004 +#define SPIFFS_ERR_NOT_FINALIZED -10005 +#define SPIFFS_ERR_NOT_INDEX -10006 +#define SPIFFS_ERR_OUT_OF_FILE_DESCS -10007 +#define SPIFFS_ERR_FILE_CLOSED -10008 +#define SPIFFS_ERR_FILE_DELETED -10009 +#define SPIFFS_ERR_BAD_DESCRIPTOR -10010 +#define SPIFFS_ERR_IS_INDEX -10011 +#define SPIFFS_ERR_IS_FREE -10012 +#define SPIFFS_ERR_INDEX_SPAN_MISMATCH -10013 +#define SPIFFS_ERR_DATA_SPAN_MISMATCH -10014 +#define SPIFFS_ERR_INDEX_REF_FREE -10015 +#define SPIFFS_ERR_INDEX_REF_LU -10016 +#define SPIFFS_ERR_INDEX_REF_INVALID -10017 +#define SPIFFS_ERR_INDEX_FREE -10018 +#define SPIFFS_ERR_INDEX_LU -10019 +#define SPIFFS_ERR_INDEX_INVALID -10020 +#define SPIFFS_ERR_NOT_WRITABLE -10021 +#define SPIFFS_ERR_NOT_READABLE -10022 +#define SPIFFS_ERR_CONFLICTING_NAME -10023 +#define SPIFFS_ERR_NOT_CONFIGURED -10024 + +#define SPIFFS_ERR_NOT_A_FS -10025 +#define SPIFFS_ERR_MOUNTED -10026 +#define SPIFFS_ERR_ERASE_FAIL -10027 +#define SPIFFS_ERR_MAGIC_NOT_POSSIBLE -10028 + +#define SPIFFS_ERR_NO_DELETED_BLOCKS -10029 + +#define SPIFFS_ERR_FILE_EXISTS -10030 + +#define SPIFFS_ERR_NOT_A_FILE -10031 +#define SPIFFS_ERR_RO_NOT_IMPL -10032 +#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033 +#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034 +#define SPIFFS_ERR_PROBE_NOT_A_FS -10035 +#define SPIFFS_ERR_NAME_TOO_LONG -10036 + +#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037 +#define SPIFFS_ERR_IX_MAP_MAPPED -10038 +#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039 + +#define SPIFFS_ERR_INTERNAL -10050 + +#define SPIFFS_ERR_TEST -10100 + + +// spiffs file descriptor index type. must be signed +typedef s16_t spiffs_file; +// spiffs file descriptor flags +typedef u16_t spiffs_flags; +// spiffs file mode +typedef u16_t spiffs_mode; +// object type +typedef u8_t spiffs_obj_type; + +struct spiffs_t; + +#if SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system check callback report operation */ +typedef enum { + SPIFFS_CHECK_LOOKUP = 0, + SPIFFS_CHECK_INDEX, + SPIFFS_CHECK_PAGE +} spiffs_check_type; + +/* file system check callback report type */ +typedef enum { + SPIFFS_CHECK_PROGRESS = 0, + SPIFFS_CHECK_ERROR, + SPIFFS_CHECK_FIX_INDEX, + SPIFFS_CHECK_FIX_LOOKUP, + SPIFFS_CHECK_DELETE_ORPHANED_INDEX, + SPIFFS_CHECK_DELETE_PAGE, + SPIFFS_CHECK_DELETE_BAD_FILE +} spiffs_check_report; + +/* file system check callback function */ +#if SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#else // SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system listener callback operation */ +typedef enum { + /* the file has been created */ + SPIFFS_CB_CREATED = 0, + /* the file has been updated or moved to another page */ + SPIFFS_CB_UPDATED, + /* the file has been deleted */ + SPIFFS_CB_DELETED +} spiffs_fileop_type; + +/* file system listener callback function */ +typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix); + +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) \ + printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) printf(__VA_ARGS__) +#endif + +/* Any write to the filehandle is appended to end of the file */ +#define SPIFFS_APPEND (1<<0) +#define SPIFFS_O_APPEND SPIFFS_APPEND +/* If the opened file exists, it will be truncated to zero length before opened */ +#define SPIFFS_TRUNC (1<<1) +#define SPIFFS_O_TRUNC SPIFFS_TRUNC +/* If the opened file does not exist, it will be created before opened */ +#define SPIFFS_CREAT (1<<2) +#define SPIFFS_O_CREAT SPIFFS_CREAT +/* The opened file may only be read */ +#define SPIFFS_RDONLY (1<<3) +#define SPIFFS_O_RDONLY SPIFFS_RDONLY +/* The opened file may only be written */ +#define SPIFFS_WRONLY (1<<4) +#define SPIFFS_O_WRONLY SPIFFS_WRONLY +/* The opened file may be both read and written */ +#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) +#define SPIFFS_O_RDWR SPIFFS_RDWR +/* Any writes to the filehandle will never be cached but flushed directly */ +#define SPIFFS_DIRECT (1<<5) +#define SPIFFS_O_DIRECT SPIFFS_DIRECT +/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */ +#define SPIFFS_EXCL (1<<6) +#define SPIFFS_O_EXCL SPIFFS_EXCL + +#define SPIFFS_SEEK_SET (0) +#define SPIFFS_SEEK_CUR (1) +#define SPIFFS_SEEK_END (2) + +#define SPIFFS_TYPE_FILE (1) +#define SPIFFS_TYPE_DIR (2) +#define SPIFFS_TYPE_HARD_LINK (3) +#define SPIFFS_TYPE_SOFT_LINK (4) + +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) +#endif + +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) +#endif + +// phys structs + +// spiffs spi configuration struct +typedef struct { + // physical read function + spiffs_read hal_read_f; + // physical write function + spiffs_write hal_write_f; + // physical erase function + spiffs_erase hal_erase_f; +#if SPIFFS_SINGLETON == 0 + // physical size of the spi flash + u32_t phys_size; + // physical offset in spi flash used for spiffs, + // must be on block boundary + u32_t phys_addr; + // physical size when erasing a block + u32_t phys_erase_block; + + // logical size of a block, must be on physical + // block size boundary and must never be less than + // a physical block + u32_t log_block_size; + // logical size of a page, must be at least + // log_block_size / 8 + u32_t log_page_size; + +#endif +#if SPIFFS_FILEHDL_OFFSET + // an integer offset added to each file handle + u16_t fh_ix_offset; +#endif +} spiffs_config; + +typedef struct spiffs_t { + // file system configuration + spiffs_config cfg; + // number of logical blocks + u32_t block_count; + + // cursor for free blocks, block index + spiffs_block_ix free_cursor_block_ix; + // cursor for free blocks, entry index + int free_cursor_obj_lu_entry; + // cursor when searching, block index + spiffs_block_ix cursor_block_ix; + // cursor when searching, entry index + int cursor_obj_lu_entry; + + // primary work buffer, size of a logical page + u8_t *lu_work; + // secondary work buffer, size of a logical page + u8_t *work; + // file descriptor memory area + u8_t *fd_space; + // available file descriptors + u32_t fd_count; + + // last error + s32_t err_code; + + // current number of free blocks + u32_t free_blocks; + // current number of busy pages + u32_t stats_p_allocated; + // current number of deleted pages + u32_t stats_p_deleted; + // flag indicating that garbage collector is cleaning + u8_t cleaning; + // max erase count amongst all blocks + spiffs_obj_id max_erase_count; + +#if SPIFFS_GC_STATS + u32_t stats_gc_runs; +#endif + +#if SPIFFS_CACHE + // cache memory + void *cache; + // cache size + u32_t cache_size; +#if SPIFFS_CACHE_STATS + u32_t cache_hits; + u32_t cache_misses; +#endif +#endif + + // check callback function + spiffs_check_callback check_cb_f; + // file callback function + spiffs_file_callback file_cb_f; + // mounted flag + u8_t mounted; + // user data + void *user_data; + // config magic + u32_t config_magic; +} spiffs; + +/* spiffs file status struct */ +typedef struct { + spiffs_obj_id obj_id; + u32_t size; + spiffs_obj_type type; + spiffs_page_ix pix; + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_stat; + +struct spiffs_dirent { + spiffs_obj_id obj_id; + u8_t name[SPIFFS_OBJ_NAME_LEN]; + spiffs_obj_type type; + u32_t size; + spiffs_page_ix pix; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +}; + +typedef struct { + spiffs *fs; + spiffs_block_ix block; + int entry; +} spiffs_DIR; + +#if SPIFFS_IX_MAP + +typedef struct { + // buffer with looked up data pixes + spiffs_page_ix *map_buf; + // precise file byte offset + u32_t offset; + // start data span index of lookup buffer + spiffs_span_ix start_spix; + // end data span index of lookup buffer + spiffs_span_ix end_spix; +} spiffs_ix_map; + +#endif + +// functions + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +/** + * Special function. This takes a spiffs config struct and returns the number + * of blocks this file system was formatted with. This function relies on + * that following info is set correctly in given config struct: + * + * phys_addr, log_page_size, and log_block_size. + * + * Also, hal_read_f must be set in the config struct. + * + * One must be sure of the correct page size and that the physical address is + * correct in the probed file system when calling this function. It is not + * checked if the phys_addr actually points to the start of the file system, + * so one might get a false positive if entering a phys_addr somewhere in the + * middle of the file system at block boundary. In addition, it is not checked + * if the page size is actually correct. If it is not, weird file system sizes + * will be returned. + * + * If this function detects a file system it returns the assumed file system + * size, which can be used to set the phys_size. + * + * Otherwise, it returns an error indicating why it is not regarded as a file + * system. + * + * Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK + * macros. It returns the error code directly, instead of as read by + * SPIFFS_errno. + * + * @param config essential parts of the physical and logical + * configuration of the file system. + */ +s32_t SPIFFS_probe_fs(spiffs_config *config); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +/** + * Initializes the file system dynamic parameters and mounts the filesystem. + * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS + * if the flash does not contain a recognizable file system. + * In this case, SPIFFS_format must be called prior to remounting. + * @param fs the file system struct + * @param config the physical and logical configuration of the file system + * @param work a memory work buffer comprising 2*config->log_page_size + * bytes used throughout all file system operations + * @param fd_space memory for file descriptors + * @param fd_space_size memory size of file descriptors + * @param cache memory for cache, may be null + * @param cache_size memory size of cache + * @param check_cb_f callback function for reporting during consistency checks + */ +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f); + +/** + * Unmounts the file system. All file handles will be flushed of any + * cached writes and closed. + * @param fs the file system struct + */ +void SPIFFS_unmount(spiffs *fs); + +/** + * Creates a new file. + * @param fs the file system struct + * @param path the path of the new file + * @param mode ignored, for posix compliance + */ +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode); + +/** + * Opens/creates a file. + * @param fs the file system struct + * @param path the path of the new file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY, + * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given dir entry. + * Optimization purposes, when traversing a file system with SPIFFS_readdir + * a normal SPIFFS_open would need to traverse the filesystem again to find + * the file, whilst SPIFFS_open_by_dirent already knows where the file resides. + * @param fs the file system struct + * @param e the dir entry to the file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given page index. + * Optimization purposes, opens a file by directly pointing to the page + * index in the spi flash. + * If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE + * is returned. + * @param fs the file system struct + * @param page_ix the page index + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode); + +/** + * Reads from given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf where to put read data + * @param len how much to read + * @returns number of bytes read, or -1 if error + */ +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Writes to given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf the data to write + * @param len how much to write + * @returns number of bytes written, or -1 if error + */ +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Moves the read/write file offset. Resulting offset is returned or negative if error. + * lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset. + * @param fs the file system struct + * @param fh the filehandle + * @param offs how much/where to move the offset + * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes + * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset + * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative + */ +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); + +/** + * Removes a file by path + * @param fs the file system struct + * @param path the path of the file to remove + */ +s32_t SPIFFS_remove(spiffs *fs, const char *path); + +/** + * Removes a file by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to remove + */ +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh); + +/** + * Gets file status by path + * @param fs the file system struct + * @param path the path of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s); + +/** + * Gets file status by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s); + +/** + * Flushes all pending write operations from cache for given file + * @param fs the file system struct + * @param fh the filehandle of the file to flush + */ +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh); + +/** + * Closes a filehandle. If there are pending write operations, these are finalized before closing. + * @param fs the file system struct + * @param fh the filehandle of the file to close + */ +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh); + +/** + * Renames a file + * @param fs the file system struct + * @param old path of file to rename + * @param newPath new path of file + */ +s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath); + +#if SPIFFS_OBJ_META_LEN +/** + * Updates file's metadata + * @param fs the file system struct + * @param path path to the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta); + +/** + * Updates file's metadata + * @param fs the file system struct + * @param fh file handle of the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta); +#endif + +/** + * Returns last error of last file operation. + * @param fs the file system struct + */ +s32_t SPIFFS_errno(spiffs *fs); + +/** + * Clears last error. + * @param fs the file system struct + */ +void SPIFFS_clearerr(spiffs *fs); + +/** + * Opens a directory stream corresponding to the given name. + * The stream is positioned at the first entry in the directory. + * On hydrogen builds the name argument is ignored as hydrogen builds always correspond + * to a flat file structure - no directories. + * @param fs the file system struct + * @param name the name of the directory + * @param d pointer the directory stream to be populated + */ +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d); + +/** + * Closes a directory stream + * @param d the directory stream to close + */ +s32_t SPIFFS_closedir(spiffs_DIR *d); + +/** + * Reads a directory into given spifs_dirent struct. + * @param d pointer to the directory stream + * @param e the dirent struct to be populated + * @returns null if error or end of stream, else given dirent is returned + */ +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e); + +/** + * Runs a consistency check on given filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_check(spiffs *fs); + +/** + * Returns number of total bytes available and number of used bytes. + * This is an estimation, and depends on if there a many files with little + * data or few files with much data. + * NB: If used number of bytes exceeds total bytes, a SPIFFS_check should + * run. This indicates a power loss in midst of things. In worst case + * (repeated powerlosses in mending or gc) you might have to delete some files. + * + * @param fs the file system struct + * @param total total number of bytes in filesystem + * @param used used number of bytes in filesystem + */ +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used); + +/** + * Formats the entire file system. All data will be lost. + * The filesystem must not be mounted when calling this. + * + * NB: formatting is awkward. Due to backwards compatibility, SPIFFS_mount + * MUST be called prior to formatting in order to configure the filesystem. + * If SPIFFS_mount succeeds, SPIFFS_unmount must be called before calling + * SPIFFS_format. + * If SPIFFS_mount fails, SPIFFS_format can be called directly without calling + * SPIFFS_unmount first. + * + * @param fs the file system struct + */ +s32_t SPIFFS_format(spiffs *fs); + +/** + * Returns nonzero if spiffs is mounted, or zero if unmounted. + * @param fs the file system struct + */ +u8_t SPIFFS_mounted(spiffs *fs); + +/** + * Tries to find a block where most or all pages are deleted, and erase that + * block if found. Does not care for wear levelling. Will not move pages + * around. + * If parameter max_free_pages are set to 0, only blocks with only deleted + * pages will be selected. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * Setting max_free_pages to anything larger than zero will eventually wear + * flash more as a block containing free pages can be erased. + * + * Will set err_no to SPIFFS_OK if a block was found and erased, + * SPIFFS_ERR_NO_DELETED_BLOCK if no matching block was found, + * or other error. + * + * @param fs the file system struct + * @param max_free_pages maximum number allowed free pages in block + */ +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages); + +/** + * Will try to make room for given amount of bytes in the filesystem by moving + * pages and erasing blocks. + * If it is physically impossible, err_no will be set to SPIFFS_ERR_FULL. If + * there already is this amount (or more) of free space, SPIFFS_gc will + * silently return. It is recommended to call SPIFFS_info before invoking + * this method in order to determine what amount of bytes to give. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * @param fs the file system struct + * @param size amount of bytes that should be freed + */ +s32_t SPIFFS_gc(spiffs *fs, u32_t size); + +/** + * Check if EOF reached. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh); + +/** + * Get position in file. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh); + +/** + * Registers a callback function that keeps track on operations on file + * headers. Do note, that this callback is called from within internal spiffs + * mechanisms. Any operations on the actual file system being callbacked from + * in this callback will mess things up for sure - do not do this. + * This can be used to track where files are and move around during garbage + * collection, which in turn can be used to build location tables in ram. + * Used in conjuction with SPIFFS_open_by_page this may improve performance + * when opening a lot of files. + * Must be invoked after mount. + * + * @param fs the file system struct + * @param cb_func the callback on file operations + */ +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func); + +#if SPIFFS_IX_MAP + +/** + * Maps the first level index lookup to a given memory map. + * This will make reading big files faster, as the memory map will be used for + * looking up data pages instead of searching for the indices on the physical + * medium. When mapping, all affected indicies are found and the information is + * copied to the array. + * Whole file or only parts of it may be mapped. The index map will cover file + * contents from argument offset until and including arguments (offset+len). + * It is valid to map a longer range than the current file size. The map will + * then be populated when the file grows. + * On garbage collections and file data page movements, the map array will be + * automatically updated. Do not tamper with the map array, as this contains + * the references to the data pages. Modifying it from outside will corrupt any + * future readings using this file descriptor. + * The map will no longer be used when the file descriptor closed or the file + * is unmapped. + * This can be useful to get faster and more deterministic timing when reading + * large files, or when seeking and reading a lot within a file. + * @param fs the file system struct + * @param fh the file handle of the file to map + * @param map a spiffs_ix_map struct, describing the index map + * @param offset absolute file offset where to start the index map + * @param len length of the mapping in actual file bytes + * @param map_buf the array buffer for the look up data - number of required + * elements in the array can be derived from function + * SPIFFS_bytes_to_ix_map_entries given the length + */ +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf); + +/** + * Unmaps the index lookup from this filehandle. All future readings will + * proceed as normal, requiring reading of the first level indices from + * physical media. + * The map and map buffer given in function SPIFFS_ix_map will no longer be + * referenced by spiffs. + * It is not strictly necessary to unmap a file before closing it, as closing + * a file will automatically unmap it. + * @param fs the file system struct + * @param fh the file handle of the file to unmap + */ +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh); + +/** + * Moves the offset for the index map given in function SPIFFS_ix_map. Parts or + * all of the map buffer will repopulated. + * @param fs the file system struct + * @param fh the mapped file handle of the file to remap + * @param offset new absolute file offset where to start the index map + */ +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs); + +/** + * Utility function to get number of spiffs_page_ix entries a map buffer must + * contain on order to map given amount of file data in bytes. + * See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes. + * @param fs the file system struct + * @param bytes number of file data bytes to map + * @return needed number of elements in a spiffs_page_ix array needed to + * map given amount of bytes in a file + */ +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes); + +/** + * Utility function to amount of file data bytes that can be mapped when + * mapping a file with buffer having given number of spiffs_page_ix entries. + * See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries. + * @param fs the file system struct + * @param map_page_ix_entries number of entries in a spiffs_page_ix array + * @return amount of file data in bytes that can be mapped given a map + * buffer having given amount of spiffs_page_ix entries + */ +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries); + +#endif // SPIFFS_IX_MAP + + +#if SPIFFS_TEST_VISUALISATION +/** + * Prints out a visualization of the filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_vis(spiffs *fs); +#endif + +#if SPIFFS_BUFFER_HELP +/** + * Returns number of bytes needed for the filedescriptor buffer given + * amount of file descriptors. + */ +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs); + +#if SPIFFS_CACHE +/** + * Returns number of bytes needed for the cache buffer given + * amount of cache pages. + */ +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages); +#endif +#endif + +#if SPIFFS_CACHE +#endif +#if defined(__cplusplus) +} +#endif + +#endif /* SPIFFS_H_ */ diff --git a/components/mkspiffs/src/spiffs/spiffs_cache.c b/components/mkspiffs/src/spiffs/spiffs_cache.c new file mode 100644 index 0000000..018f763 --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_cache.c @@ -0,0 +1,314 @@ +/* + * spiffs_cache.c + * + * Created on: Jun 23, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_CACHE + +// returns cached page for give page index, or null if no such cached page +static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache *cache = spiffs_get_cache(fs); + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0; + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + cp->pix == pix ) { + SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix); + cp->last_access = cache->last_access; + return cp; + } + } + //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix); + return 0; +} + +// frees cached page +static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, ix); + if (cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { + u8_t *mem = spiffs_get_cache_page(fs, cache, ix); + res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); + } + + cp->flags = 0; + cache->cpage_use_map &= ~(1 << ix); + + if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id); + } else { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix); + } + } + + return res; +} + +// removes the oldest accessed cached page +static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) { + // at least one free cpage + return SPIFFS_OK; + } + + // all busy, scan thru all to find the cpage which has oldest access + int i; + int cand_ix = -1; + u32_t oldest_val = 0; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->last_access - cp->last_access) > oldest_val && + (cp->flags & flag_mask) == flags) { + oldest_val = cache->last_access - cp->last_access; + cand_ix = i; + } + } + + if (cand_ix >= 0) { + res = spiffs_cache_page_free(fs, cand_ix, 1); + } + + return res; +} + +// allocates a new cached page and returns it, or null if all cache pages are busy +static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) { + spiffs_cache *cache = spiffs_get_cache(fs); + if (cache->cpage_use_map == 0xffffffff) { + // out of cache memory + return 0; + } + int i; + for (i = 0; i < cache->cpage_count; i++) { + if ((cache->cpage_use_map & (1<cpage_use_map |= (1<last_access = cache->last_access; + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i); + return cp; + } + } + // out of cache entries + return 0; +} + +// drops the cache page for give page index +void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + if (cp) { + spiffs_cache_page_free(fs, cp->ix, 0); + } +} + +// ------------------------------ + +// reads from spi flash or the cache +s32_t spiffs_phys_rd( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *dst) { + (void)fh; + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); + cache->last_access++; + if (cp) { + // we've already got one, you see +#if SPIFFS_CACHE_STATS + fs->cache_hits++; +#endif + cp->last_access = cache->last_access; + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { + // for second layer lookup functions, we do not cache in order to prevent shredding + return SPIFFS_HAL_READ(fs, addr, len, dst); + } +#if SPIFFS_CACHE_STATS + fs->cache_misses++; +#endif + // this operation will always free one cache page (unless all already free), + // the result code stems from the write operation of the possibly freed cache page + res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + + cp = spiffs_cache_page_allocate(fs); + if (cp) { + cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; + cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + + s32_t res2 = SPIFFS_HAL_READ(fs, + addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + spiffs_get_cache_page(fs, cache, cp->ix)); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + // this will never happen, last resort for sake of symmetry + s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + } + } + return res; +} + +// writes to spi flash and/or the cache +s32_t spiffs_phys_wr( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *src) { + (void)fh; + spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + + if (cp && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) { + // have a cache page + // copy in data to cache page + + if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE && + (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { + // page is being deleted, wipe from cache - unless it is a lookup page + spiffs_cache_page_free(fs, cp->ix, 0); + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } + + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); + + cache->last_access++; + cp->last_access = cache->last_access; + + if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { + // page is being updated, no write-cache, just pass thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } else { + return SPIFFS_OK; + } + } else { + // no cache page, no write cache - just write thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } +} + +#if SPIFFS_CACHE_WR +// returns the cache page that this fd refers, or null if no cache page +spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) { + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { + // all cpages free, no cpage cannot be assigned to obj_id + return 0; + } + + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) && + cp->obj_id == fd->obj_id) { + return cp; + } + } + + return 0; +} + +// allocates a new cache page and refers this to given fd - flushes an old cache +// page if all cache is busy +spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) { + // before this function is called, it is ensured that there is no already existing + // cache page with same object id + spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + spiffs_cache_page *cp = spiffs_cache_page_allocate(fs); + if (cp == 0) { + // could not get cache page + return 0; + } + + cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; + cp->obj_id = fd->obj_id; + fd->cache_page = cp; + return cp; +} + +// unrefers all fds that this cache page refers to and releases the cache page +void spiffs_cache_fd_release(spiffs *fs, spiffs_cache_page *cp) { + if (cp == 0) return; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0 && cur_fd->cache_page == cp) { + cur_fd->cache_page = 0; + } + } + spiffs_cache_page_free(fs, cp->ix, 0); + + cp->obj_id = 0; +} + +#endif + +// initializes the cache +void spiffs_cache_init(spiffs *fs) { + if (fs->cache == 0) return; + u32_t sz = fs->cache_size; + u32_t cache_mask = 0; + int i; + int cache_entries = + (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs)); + if (cache_entries <= 0) return; + + for (i = 0; i < cache_entries; i++) { + cache_mask <<= 1; + cache_mask |= 1; + } + + spiffs_cache cache; + memset(&cache, 0, sizeof(spiffs_cache)); + cache.cpage_count = cache_entries; + cache.cpages = (u8_t *)((u8_t *)fs->cache + sizeof(spiffs_cache)); + + cache.cpage_use_map = 0xffffffff; + cache.cpage_use_mask = cache_mask; + memcpy(fs->cache, &cache, sizeof(spiffs_cache)); + + spiffs_cache *c = spiffs_get_cache(fs); + + memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs)); + + c->cpage_use_map &= ~(c->cpage_use_mask); + for (i = 0; i < cache.cpage_count; i++) { + spiffs_get_cache_page_hdr(fs, c, i)->ix = i; + } +} + +#endif // SPIFFS_CACHE diff --git a/components/mkspiffs/src/spiffs/spiffs_check.c b/components/mkspiffs/src/spiffs/spiffs_check.c new file mode 100644 index 0000000..dde85ef --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_check.c @@ -0,0 +1,995 @@ +/* + * spiffs_check.c + * + * Contains functionality for checking file system consistency + * and mending problems. + * Three levels of consistency checks are implemented: + * + * Look up consistency + * Checks if indices in lookup pages are coherent with page headers + * Object index consistency + * Checks if there are any orphaned object indices (missing object index headers). + * If an object index is found but not its header, the object index is deleted. + * This is critical for the following page consistency check. + * Page consistency + * Checks for pages that ought to be indexed, ought not to be indexed, are multiple indexed + * + * + * Created on: Jul 7, 2013 + * Author: petera + */ + + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +#if SPIFFS_HAL_CALLBACK_EXTRA +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#else +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#endif + +//--------------------------------------- +// Look up consistency + +// searches in the object indices and returns the referenced page index given +// the object id and the data span index +// destroys fs->lu_work +static s32_t spiffs_object_get_data_page_index_reference( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix data_spix, + spiffs_page_ix *pix, + spiffs_page_ix *objix_pix) { + s32_t res; + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // find obj index for obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, objix_pix); + SPIFFS_CHECK_RES(res); + + // load obj index entry + u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, *objix_pix); + if (objix_spix == 0) { + // get referenced page from object index header + addr += sizeof(spiffs_page_object_ix_header) + data_spix * sizeof(spiffs_page_ix); + } else { + // get referenced page from object index + addr += sizeof(spiffs_page_object_ix) + SPIFFS_OBJ_IX_ENTRY(fs, data_spix) * sizeof(spiffs_page_ix); + } + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, addr, sizeof(spiffs_page_ix), (u8_t *)pix); + + return res; +} + +// copies page contents to a new page +static s32_t spiffs_rewrite_page(spiffs *fs, spiffs_page_ix cur_pix, spiffs_page_header *p_hdr, spiffs_page_ix *new_pix) { + s32_t res; + res = spiffs_page_allocate_data(fs, p_hdr->obj_id, p_hdr, 0,0,0,0, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, *new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + return res; +} + +// rewrites the object index for given object id and replaces the +// data page index to a new page index +static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ix data_spix, spiffs_page_ix new_data_pix, spiffs_page_ix objix_pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (objix_spix == 0) { + // calc index in index header + entry = data_spix; + } else { + // calc entry in index + entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix); + + } + // load index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + // be ultra safe, double check header against provided data + if (objix_p_hdr->obj_id != obj_id) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_OBJ_ID_MISM; + } + if (objix_p_hdr->span_ix != objix_spix) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_SPIX_MISM; + } + if ((objix_p_hdr->flags & (SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_INDEX | + SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) != + (SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_DELET)) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_FLAGS_BAD; + } + + // rewrite in mem + if (objix_spix == 0) { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + } else { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + res = spiffs_page_delete(fs, objix_pix); + + return res; +} + +// deletes an object just by marking object index header as deleted +static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) { + spiffs_page_ix objix_hdr_pix; + s32_t res; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id, 0, 0, &objix_hdr_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + return SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_IXDELE; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + return res; +} + +// validates the given look up entry +static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, spiffs_page_header *p_hdr, + spiffs_page_ix cur_pix, spiffs_block_ix cur_block, int cur_entry, int *reload_lu) { + (void)cur_block; + (void)cur_entry; + u8_t delete_page = 0; + s32_t res = SPIFFS_OK; + spiffs_page_ix objix_pix; + spiffs_page_ix ref_pix; + // check validity, take actions + if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) || + ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) { + // look up entry deleted / free but used in page header + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix); + *reload_lu = 1; + delete_page = 1; + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // header says data page + // data page can be removed if not referenced by some object index + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix == cur_pix) { + // data page referenced by object index but deleted in lu + // copy page to new place and re-write the object index to new place + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } else { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // header says index page + // index page can be removed if other index with same obj_id and spanix is found + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, 0); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no such index page found, check for a data page amongst page headers + // lu cannot be trusted + res = spiffs_obj_lu_find_id_and_span_by_phdr(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, 0); + if (res == SPIFFS_OK) { // ignore other errors + // got a data page also, assume lu corruption only, rewrite to new page + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + } + } else { + SPIFFS_CHECK_RES(res); + } + } + } + if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) { + // look up entry used + if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id); + delete_page = 1; + if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 || + (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) || + (p_hdr->flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE)) == 0) { + // page deleted or not finalized, just remove it + } else { + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // if data page, check for reference to this page + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + // if found, rewrite page with object id, update index, and delete current + if (ref_pix == cur_pix) { + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // else if index, check for other pages with both obj_id's and spanix + spiffs_page_ix objix_pix_lu, objix_pix_ph; + // see if other object index page exists for lookup obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + // if both obj_id's found, just delete current + if (objix_pix_ph == 0 || objix_pix_lu == 0) { + // otherwise try finding first corresponding data pages + spiffs_page_ix data_pix_lu, data_pix_ph; + // see if other data page exists for look up obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other data page exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL); + new_ph.span_ix = p_hdr->span_ix; + spiffs_page_ix new_pix; + if ((objix_pix_lu && data_pix_lu && data_pix_ph && objix_pix_ph == 0) || + (objix_pix_lu == 0 && data_pix_ph && objix_pix_ph == 0)) { + // got a data page for page header obj id + // rewrite as obj_id_ph + new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) || + (objix_pix_ph == 0 && data_pix_lu && objix_pix_lu == 0)) { + // got a data page for look up obj id + // rewrite as obj_id_lu + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else { + // cannot safely do anything + SPIFFS_CHECK_DBG("LU: FIXUP: nothing to do, just delete\n"); + } + } + } + } + } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) || + ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) { + SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix); + spiffs_page_ix data_pix, objix_pix_d; + // see if other data page exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &objix_pix_d); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_d = 0; + } + SPIFFS_CHECK_RES(res); + + delete_page = 1; + // if other data page exists and object index exists, just delete page + if (data_pix && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index and data page exists, simply remove\n"); + } else + // if only data page exists, make this page index + if (data_pix && objix_pix_d == 0) { + SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else + // if only index exists, make data page + if (data_pix == 0 && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else { + // if nothing exists, we cannot safely make a decision - delete + } + } + else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix); + delete_page = 1; + } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix); + // page can be removed if not referenced by object index + *reload_lu = 1; + res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + delete_page = 1; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix != cur_pix) { + SPIFFS_CHECK_DBG("LU: FIXUP: other finalized page is referred, just delete\n"); + delete_page = 1; + } else { + // page referenced by object index but not final + // just finalize + SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), (u8_t*)&flags); + } + } + } + } + + if (delete_page) { + SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + + return res; +} + +static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry, + const void *user_const_p, void *user_var_p) { + (void)user_const_p; + (void)user_var_p; + s32_t res = SPIFFS_OK; + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + int reload_lu = 0; + + res = spiffs_lookup_check_validate(fs, obj_id, &p_hdr, cur_pix, cur_block, cur_entry, &reload_lu); + SPIFFS_CHECK_RES(res); + + if (res == SPIFFS_OK) { + return reload_lu ? SPIFFS_VIS_COUNTINUE_RELOAD : SPIFFS_VIS_COUNTINUE; + } + return res; +} + + +// Scans all object look up. For each entry, corresponding page header is checked for validity. +// If an object index header page is found, this is also checked +s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) { + (void)check_all_objects; + s32_t res = SPIFFS_OK; + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); + + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); + } + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); + + return res; +} + +//--------------------------------------- +// Page consistency + +// Scans all pages (except lu pages), reserves 4 bits in working memory for each page +// bit 0: 0 == FREE|DELETED, 1 == USED +// bit 1: 0 == UNREFERENCED, 1 == REFERENCED +// bit 2: 0 == NOT_INDEX, 1 == INDEX +// bit 3: unused +// A consistent file system will have only pages being +// * x000 free, unreferenced, not index +// * x011 used, referenced only once, not index +// * x101 used, unreferenced, index +// The working memory might not fit all pages so several scans might be needed +static s32_t spiffs_page_consistency_check_i(spiffs *fs) { + const u32_t bits = 4; + const spiffs_page_ix pages_per_scan = SPIFFS_CFG_LOG_PAGE_SZ(fs) * 8 / bits; + + s32_t res = SPIFFS_OK; + spiffs_page_ix pix_offset = 0; + + // for each range of pages fitting into work memory + while (pix_offset < SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) { + // set this flag to abort all checks and rescan the page range + u8_t restart = 0; + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + spiffs_block_ix cur_block = 0; + // build consistency bitmap for id range traversing all blocks + while (!restart && cur_block < fs->block_count) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, + (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) + + ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count), + 0); + // traverse each page except for lookup pages + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; + while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { + //if ((cur_pix & 0xff) == 0) + // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n", + // cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count); + + // read header + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + u8_t within_range = (cur_pix >= pix_offset && cur_pix < pix_offset + pages_per_scan); + const u32_t pix_byte_ix = (cur_pix - pix_offset) / (8/bits); + const u8_t pix_bit_ix = (cur_pix & ((8/bits)-1)) * bits; + + if (within_range && + (p_hdr.flags & SPIFFS_PH_FLAG_DELET) && (p_hdr.flags & SPIFFS_PH_FLAG_USED) == 0) { + // used + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 0)); + } + if ((p_hdr.flags & SPIFFS_PH_FLAG_DELET) && + (p_hdr.flags & SPIFFS_PH_FLAG_IXDELE) && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) == 0) { + // found non-deleted index + if (within_range) { + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 2)); + } + + // load non-deleted index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + + // traverse index for referenced pages + spiffs_page_ix *object_page_index; + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + int entries; + int i; + spiffs_span_ix data_spix_offset; + if (p_hdr.span_ix == 0) { + // object header page index + entries = SPIFFS_OBJ_HDR_IX_LEN(fs); + data_spix_offset = 0; + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)); + } else { + // object page index + entries = SPIFFS_OBJ_IX_LEN(fs); + data_spix_offset = SPIFFS_OBJ_HDR_IX_LEN(fs) + SPIFFS_OBJ_IX_LEN(fs) * (p_hdr.span_ix - 1); + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)); + } + + // for all entries in index + for (i = 0; !restart && i < entries; i++) { + spiffs_page_ix rpix = object_page_index[i]; + u8_t rpix_within_range = rpix >= pix_offset && rpix < pix_offset + pages_per_scan; + + if ((rpix != (spiffs_page_ix)-1 && rpix > SPIFFS_MAX_PAGES(fs)) + || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) { + + // bad reference + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // check for data page elsewhere + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, 0, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // if not, allocate free page + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = data_spix_offset + i; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix); + SPIFFS_CHECK_RES(res); + SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix); + } + // remap index + SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix); + res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); + // delete file + res = spiffs_page_delete(fs, cur_pix); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + + } else if (rpix_within_range) { + + // valid reference + // read referenced page header + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + + // cross reference page header check + if (rp_hdr.obj_id != (p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) || + rp_hdr.span_ix != data_spix_offset + i || + (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) != + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n", + rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, + rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags); + // try finding correct page + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, rpix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // not found, this index is badly borked + SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + break; + } else { + // found it, so rewrite index + SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n", + data_pix, cur_pix, p_hdr.obj_id); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + } + } + else { + // mark rpix as referenced + const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits); + const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits; + if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // Here, we should have fixed all broken references - getting this means there + // must be multiple files with same object id. Only solution is to delete + // the object which is referring to this page + SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n", + p_hdr.obj_id, cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + // extra precaution, delete this page also + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + restart = 1; + } + fs->work[rpix_byte_ix] |= (1<<(rpix_bit_ix + 1)); + } + } + } // for all index entries + } // found index + + // next page + cur_pix++; + } + // next block + cur_block++; + } + // check consistency bitmap + if (!restart) { + spiffs_page_ix objix_pix; + spiffs_page_ix rpix; + + u32_t byte_ix; + u8_t bit_ix; + for (byte_ix = 0; !restart && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs); byte_ix++) { + for (bit_ix = 0; !restart && bit_ix < 8/bits; bit_ix ++) { + u8_t bitmask = (fs->work[byte_ix] >> (bit_ix * bits)) & 0x7; + spiffs_page_ix cur_pix = pix_offset + byte_ix * (8/bits) + bit_ix; + + // 000 ok - free, unreferenced, not index + + if (bitmask == 0x1) { + + // 001 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix); + + u8_t rewrite_ix_to_this = 0; + u8_t delete_page = 0; + // check corresponding object index entry + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_get_data_page_index_reference(fs, p_hdr.obj_id, p_hdr.span_ix, + &rpix, &objix_pix); + if (res == SPIFFS_OK) { + if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) { + // pointing to a bad page altogether, rewrite index to this + rewrite_ix_to_this = 1; + SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix); + } else { + // pointing to something else, check what + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + if (((p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) == rp_hdr.obj_id) && + ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) == + (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) { + // pointing to something else valid, just delete this page then + SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix); + delete_page = 1; + } else { + // pointing to something weird, update index to point to this page instead + if (rpix != cur_pix) { + SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix, + (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ", + (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ", + (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "", + (rp_hdr.flags & SPIFFS_PH_FLAG_FINAL) ? "NOTFINAL " : "", + cur_pix); + rewrite_ix_to_this = 1; + } else { + // should not happen, destined for fubar + } + } + } + } else if (res == SPIFFS_ERR_NOT_FOUND) { + SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix); + delete_page = 1; + res = SPIFFS_OK; + } + + if (rewrite_ix_to_this) { + // if pointing to invalid page, redirect index to this page + SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n", + p_hdr.obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + continue; + } else if (delete_page) { + SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + } + SPIFFS_CHECK_RES(res); + } + if (bitmask == 0x2) { + + // 010 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + + // 011 ok - busy, referenced, not index + + if (bitmask == 0x4) { + + // 100 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix); + + // this should never happen, major fubar + } + + // 101 ok - busy, unreferenced, index + + if (bitmask == 0x6) { + + // 110 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + if (bitmask == 0x7) { + + // 111 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + } + } + } + + SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart); + // next page range + if (!restart) { + pix_offset += pages_per_scan; + } + } // while page range not reached end + return res; +} + +// Checks consistency amongst all pages and fixes irregularities +s32_t spiffs_page_consistency_check(spiffs *fs) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); + s32_t res = spiffs_page_consistency_check_i(fs); + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +//--------------------------------------- +// Object index consistency + +// searches for given object id in temporary object id index, +// returns the index or -1 +static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) { + u32_t i; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id); i++) { + if ((obj_table[i] & ~SPIFFS_OBJ_ID_IX_FLAG) == obj_id) { + return i; + } + } + return -1; +} + +static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, + int cur_entry, const void *user_const_p, void *user_var_p) { + (void)user_const_p; + s32_t res_c = SPIFFS_VIS_COUNTINUE; + s32_t res = SPIFFS_OK; + u32_t *log_ix = (u32_t*)user_var_p; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + if (p_hdr.span_ix == 0 && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET)) { + SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + return res_c; + } + + if ((p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + return res_c; + } + + if (p_hdr.span_ix == 0) { + // objix header page, register objid as reachable + int r = spiffs_object_index_search(fs, obj_id); + if (r == -1) { + // not registered, do it + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } + } else { // span index + // objix page, see if header can be found + int r = spiffs_object_index_search(fs, obj_id); + u8_t delete = 0; + if (r == -1) { + // not in temporary index, try finding it + spiffs_page_ix objix_hdr_pix; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &objix_hdr_pix); + res_c = SPIFFS_VIS_COUNTINUE_RELOAD; + if (res == SPIFFS_OK) { + // found, register as reachable + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + } else if (res == SPIFFS_ERR_NOT_FOUND) { + // not found, register as unreachable + delete = 1; + obj_table[*log_ix] = obj_id | SPIFFS_OBJ_ID_IX_FLAG; + } else { + SPIFFS_CHECK_RES(res); + } + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } else { + // in temporary index, check reachable flag + if ((obj_table[r] & SPIFFS_OBJ_ID_IX_FLAG)) { + // registered as unreachable + delete = 1; + } + } + + if (delete) { + SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + } // span index + } // valid object index id + + return res_c; +} + +// Removes orphaned and partially deleted index pages. +// Scans for index pages. When an index page is found, corresponding index header is searched for. +// If no such page exists, the index page cannot be reached as no index header exists and must be +// deleted. +s32_t spiffs_object_index_consistency_check(spiffs *fs) { + s32_t res = SPIFFS_OK; + // impl note: + // fs->work is used for a temporary object index memory, listing found object ids and + // indicating whether they can be reached or not. Acting as a fifo if object ids cannot fit. + // In the temporary object index memory, SPIFFS_OBJ_ID_IX_FLAG bit is used to indicate + // a reachable/unreachable object id. + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + u32_t obj_id_log_ix = 0; + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix, + 0, 0); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/components/mkspiffs/src/spiffs/spiffs_config.h b/components/mkspiffs/src/spiffs/spiffs_config.h new file mode 100644 index 0000000..af1f5ec --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_config.h @@ -0,0 +1,361 @@ +/* + * spiffs_config.h + * + * Created on: Jul 3, 2013 + * Author: petera + */ + +#ifndef SPIFFS_CONFIG_H_ +#define SPIFFS_CONFIG_H_ + +// ----------- 8< ------------ +// Following includes are for the linux test build of spiffs +// These may/should/must be removed/altered/replaced in your target +#include +#include +#include +#include +#include +#include +#include +// ----------- >8 ------------ + +typedef signed int s32_t; +typedef unsigned int u32_t; +typedef signed short s16_t; +typedef unsigned short u16_t; +typedef signed char s8_t; +typedef unsigned char u8_t; + +// compile time switches + +// Set generic spiffs debug output call. +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for garbage collecting. +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for caching. +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for system consistency checks. +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__) +#endif + + +// Defines spiffs debug print formatters +// some general signed number +#ifndef _SPIPRIi +#define _SPIPRIi "%d" +#endif +// address +#ifndef _SPIPRIad +#define _SPIPRIad "%08x" +#endif +// block +#ifndef _SPIPRIbl +#define _SPIPRIbl "%04x" +#endif +// page +#ifndef _SPIPRIpg +#define _SPIPRIpg "%04x" +#endif +// span index +#ifndef _SPIPRIsp +#define _SPIPRIsp "%04x" +#endif +// file descriptor +#ifndef _SPIPRIfd +#define _SPIPRIfd "%d" +#endif +// file object id +#ifndef _SPIPRIid +#define _SPIPRIid "%04x" +#endif +// file flags +#ifndef _SPIPRIfl +#define _SPIPRIfl "%02x" +#endif + +// Enable/disable API functions to determine exact number of bytes +// for filedescriptor and cache buffers. Once decided for a configuration, +// this can be disabled to reduce flash. +#ifndef SPIFFS_BUFFER_HELP +#define SPIFFS_BUFFER_HELP 0 +#endif + +// Enables/disable memory read caching of nucleus file system operations. +// If enabled, memory area must be provided for cache in SPIFFS_mount. +#ifndef SPIFFS_CACHE +#define SPIFFS_CACHE 1 +#endif +#if SPIFFS_CACHE +// Enables memory write caching for file descriptors in hydrogen +#ifndef SPIFFS_CACHE_WR +#define SPIFFS_CACHE_WR 1 +#endif + +// Enable/disable statistics on caching. Debug/test purpose only. +#ifndef SPIFFS_CACHE_STATS +#define SPIFFS_CACHE_STATS 0 +#endif +#endif + +// Always check header of each accessed page to ensure consistent state. +// If enabled it will increase number of reads, will increase flash. +#ifndef SPIFFS_PAGE_CHECK +#define SPIFFS_PAGE_CHECK 1 +#endif + +// Define maximum number of gc runs to perform to reach desired free pages. +#ifndef SPIFFS_GC_MAX_RUNS +#define SPIFFS_GC_MAX_RUNS 5 +#endif + +// Enable/disable statistics on gc. Debug/test purpose only. +#ifndef SPIFFS_GC_STATS +#define SPIFFS_GC_STATS 0 +#endif + +// Garbage collecting examines all pages in a block which and sums up +// to a block score. Deleted pages normally gives positive score and +// used pages normally gives a negative score (as these must be moved). +// To have a fair wear-leveling, the erase age is also included in score, +// whose factor normally is the most positive. +// The larger the score, the more likely it is that the block will +// picked for garbage collection. + +// Garbage collecting heuristics - weight used for deleted pages. +#ifndef SPIFFS_GC_HEUR_W_DELET +#define SPIFFS_GC_HEUR_W_DELET (5) +#endif +// Garbage collecting heuristics - weight used for used pages. +#ifndef SPIFFS_GC_HEUR_W_USED +#define SPIFFS_GC_HEUR_W_USED (-1) +#endif +// Garbage collecting heuristics - weight used for time between +// last erased and erase of this block. +#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE +#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) +#endif + +// Object name maximum length. Note that this length include the +// zero-termination character, meaning maximum string of characters +// can at most be SPIFFS_OBJ_NAME_LEN - 1. +#ifndef SPIFFS_OBJ_NAME_LEN +#define SPIFFS_OBJ_NAME_LEN (64) +#endif + +// Maximum length of the metadata associated with an object. +// Setting to non-zero value enables metadata-related API but also +// changes the on-disk format, so the change is not backward-compatible. +// +// Do note: the meta length must never exceed +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// +// This is derived from following: +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + +// spiffs_object_ix_header fields + at least some LUT entries) +#ifndef SPIFFS_OBJ_META_LEN +#define SPIFFS_OBJ_META_LEN (64) +#endif + +// Size of buffer allocated on stack used when copying data. +// Lower value generates more read/writes. No meaning having it bigger +// than logical page size. +#ifndef SPIFFS_COPY_BUFFER_STACK +#define SPIFFS_COPY_BUFFER_STACK (256) +#endif + +// Enable this to have an identifiable spiffs filesystem. This will look for +// a magic in all sectors to determine if this is a valid spiffs system or +// not on mount point. If not, SPIFFS_format must be called prior to mounting +// again. +#ifndef SPIFFS_USE_MAGIC +#define SPIFFS_USE_MAGIC (1) +#endif + +#if SPIFFS_USE_MAGIC +// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is +// enabled, the magic will also be dependent on the length of the filesystem. +// For example, a filesystem configured and formatted for 4 megabytes will not +// be accepted for mounting with a configuration defining the filesystem as 2 +// megabytes. +#ifndef SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_USE_MAGIC_LENGTH (1) +#endif +#endif + +// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level +// These should be defined on a multithreaded system + +// define this to enter a mutex if you're running on a multithreaded system +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) +#endif +// define this to exit a mutex if you're running on a multithreaded system +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) +#endif + +// Enable if only one spiffs instance with constant configuration will exist +// on the target. This will reduce calculations, flash and memory accesses. +// Parts of configuration must be defined below instead of at time of mount. +#ifndef SPIFFS_SINGLETON +#define SPIFFS_SINGLETON 0 +#endif + +#if SPIFFS_SINGLETON +// Instead of giving parameters in config struct, singleton build must +// give parameters in defines below. +#ifndef SPIFFS_CFG_PHYS_SZ +#define SPIFFS_CFG_PHYS_SZ(ignore) (1024*1024*2) +#endif +#ifndef SPIFFS_CFG_PHYS_ERASE_SZ +#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (65536) +#endif +#ifndef SPIFFS_CFG_PHYS_ADDR +#define SPIFFS_CFG_PHYS_ADDR(ignore) (0) +#endif +#ifndef SPIFFS_CFG_LOG_PAGE_SZ +#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (256) +#endif +#ifndef SPIFFS_CFG_LOG_BLOCK_SZ +#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (65536) +#endif +#endif + +// Enable this if your target needs aligned data for index tables +#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES +#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1 +#endif + +// Enable this if you want the HAL callbacks to be called with the spiffs struct +#ifndef SPIFFS_HAL_CALLBACK_EXTRA +#define SPIFFS_HAL_CALLBACK_EXTRA 0 +#endif + +// Enable this if you want to add an integer offset to all file handles +// (spiffs_file). This is useful if running multiple instances of spiffs on +// same target, in order to recognise to what spiffs instance a file handle +// belongs. +// NB: This adds config field fh_ix_offset in the configuration struct when +// mounting, which must be defined. +#ifndef SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FILEHDL_OFFSET 0 +#endif + +// Enable this to compile a read only version of spiffs. +// This will reduce binary size of spiffs. All code comprising modification +// of the file system will not be compiled. Some config will be ignored. +// HAL functions for erasing and writing to spi-flash may be null. Cache +// can be disabled for even further binary size reduction (and ram savings). +// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. +// If the file system cannot be mounted due to aborted erase operation and +// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be +// returned. +// Might be useful for e.g. bootloaders and such. +#ifndef SPIFFS_READ_ONLY +#define SPIFFS_READ_ONLY 0 +#endif + +// Enable this to add a temporal file cache using the fd buffer. +// The effects of the cache is that SPIFFS_open will find the file faster in +// certain cases. It will make it a lot easier for spiffs to find files +// opened frequently, reducing number of readings from the spi flash for +// finding those files. +// This will grow each fd by 6 bytes. If your files are opened in patterns +// with a degree of temporal locality, the system is optimized. +// Examples can be letting spiffs serve web content, where one file is the css. +// The css is accessed for each html file that is opened, meaning it is +// accessed almost every second time a file is opened. Another example could be +// a log file that is often opened, written, and closed. +// The size of the cache is number of given file descriptors, as it piggybacks +// on the fd update mechanism. The cache lives in the closed file descriptors. +// When closed, the fd know the whereabouts of the file. Instead of forgetting +// this, the temporal cache will keep handling updates to that file even if the +// fd is closed. If the file is opened again, the location of the file is found +// directly. If all available descriptors become opened, all cache memory is +// lost. +#ifndef SPIFFS_TEMPORAL_FD_CACHE +#define SPIFFS_TEMPORAL_FD_CACHE 1 +#endif + +// Temporal file cache hit score. Each time a file is opened, all cached files +// will lose one point. If the opened file is found in cache, that entry will +// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this +// value for the specific access patterns of the application. However, it must +// be between 1 (no gain for hitting a cached entry often) and 255. +#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE +#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 8 +#endif + +// Enable to be able to map object indices to memory. +// This allows for faster and more deterministic reading if cases of reading +// large files and when changing file offset by seeking around a lot. +// When mapping a file's index, the file system will be scanned for index pages +// and the info will be put in memory provided by user. When reading, the +// memory map can be looked up instead of searching for index pages on the +// medium. This way, user can trade memory against performance. +// Whole, parts of, or future parts not being written yet can be mapped. The +// memory array will be owned by spiffs and updated accordingly during garbage +// collecting or when modifying the indices. The latter is invoked by when the +// file is modified in some way. The index buffer is tied to the file +// descriptor. +#ifndef SPIFFS_IX_MAP +#define SPIFFS_IX_MAP 1 +#endif + +// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function +// in the api. This function will visualize all filesystem using given printf +// function. +#ifndef SPIFFS_TEST_VISUALISATION +#define SPIFFS_TEST_VISUALISATION 0 +#endif +#if SPIFFS_TEST_VISUALISATION +#ifndef spiffs_printf +#define spiffs_printf(...) printf(__VA_ARGS__) +#endif +// spiffs_printf argument for a free page +#ifndef SPIFFS_TEST_VIS_FREE_STR +#define SPIFFS_TEST_VIS_FREE_STR "_" +#endif +// spiffs_printf argument for a deleted page +#ifndef SPIFFS_TEST_VIS_DELE_STR +#define SPIFFS_TEST_VIS_DELE_STR "/" +#endif +// spiffs_printf argument for an index page for given object id +#ifndef SPIFFS_TEST_VIS_INDX_STR +#define SPIFFS_TEST_VIS_INDX_STR(id) "i" +#endif +// spiffs_printf argument for a data page for given object id +#ifndef SPIFFS_TEST_VIS_DATA_STR +#define SPIFFS_TEST_VIS_DATA_STR(id) "d" +#endif +#endif + +// Types depending on configuration such as the amount of flash bytes +// given to spiffs file system in total (spiffs_file_system_size), +// the logical block size (log_block_size), and the logical page size +// (log_page_size) + +// Block index type. Make sure the size of this type can hold +// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size +typedef u16_t spiffs_block_ix; +// Page index type. Make sure the size of this type can hold +// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size +typedef u16_t spiffs_page_ix; +// Object id type - most significant bit is reserved for index flag. Make sure the +// size of this type can hold the highest object id on a full system, +// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 +typedef u16_t spiffs_obj_id; +// Object span index type. Make sure the size of this type can +// hold the largest possible span index on the system - +// i.e. (spiffs_file_system_size / log_page_size) - 1 +typedef u16_t spiffs_span_ix; + +#endif /* SPIFFS_CONFIG_H_ */ diff --git a/components/mkspiffs/src/spiffs/spiffs_gc.c b/components/mkspiffs/src/spiffs/spiffs_gc.c new file mode 100644 index 0000000..db1af4c --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_gc.c @@ -0,0 +1,606 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +// Erases a logical block and updates the erase counter. +// If cache is enabled, all pages that might be cached in this block +// is dropped. +static s32_t spiffs_gc_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + + SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix); + res = spiffs_erase_block(fs, bix); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_CACHE + { + u32_t i; + for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) { + spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i); + } + } +#endif + return res; +} + +// Searches for blocks where all entries are deleted - if one is found, +// the block is erased. Compared to the non-quick gc, the quick one ensures +// that no updates are needed on existing objects on pages that are erased. +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + + SPIFFS_GC_DBG("gc_quick: running\n"); +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // find fully deleted blocks + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t free_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else if (obj_id == SPIFFS_OBJ_ID_FREE) { + // kill scan, go for next block + free_pages_in_block++; + if (free_pages_in_block > max_free_pages) { + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + } else { + // kill scan, go for next block + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + if (res == SPIFFS_OK && + deleted_pages_in_block + free_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs) && + free_pages_in_block <= max_free_pages) { + // found a fully deleted block + fs->stats_p_deleted -= deleted_pages_in_block; + res = spiffs_gc_erase_block(fs, cur_block); + return res; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + if (res == SPIFFS_OK) { + res = SPIFFS_ERR_NO_DELETED_BLOCKS; + } + return res; +} + +// Checks if garbage collecting is necessary. If so a candidate block is found, +// cleansed and erased +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len) { + s32_t res; + s32_t free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) + - fs->stats_p_allocated - fs->stats_p_deleted; + int tries = 0; + + if (fs->free_blocks > 3 && + (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + return SPIFFS_OK; + } + + u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); +// if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { +// SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); +// return SPIFFS_ERR_FULL; +// } + if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { + SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); + return SPIFFS_ERR_FULL; + } + + do { + SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n", + tries, + fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), + len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs))); + + spiffs_block_ix *cands; + int count; + spiffs_block_ix cand; + s32_t prev_free_pages = free_pages; + // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state + res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); + SPIFFS_CHECK_RES(res); + if (count == 0) { + SPIFFS_GC_DBG("gc_check: no candidates, return\n"); + return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL; + } +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + cand = cands[0]; + fs->cleaning = 1; + //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand); + res = spiffs_gc_clean(fs, cand); + fs->cleaning = 0; + if (res < 0) { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } else { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_page_stats(fs, cand); + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_block(fs, cand); + SPIFFS_CHECK_RES(res); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + + if (prev_free_pages <= 0 && prev_free_pages == free_pages) { + // abort early to reduce wear, at least tried once + SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n"); + break; + } + + } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || + (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + res = SPIFFS_ERR_FULL; + } + + SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n", + fs->stats_p_allocated + fs->stats_p_deleted, + fs->free_blocks, free_pages, tries, res); + + return res; +} + +// Updates page statistics for a block that is about to be erased +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + int obj_lookup_page = 0; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + u32_t dele = 0; + u32_t allo = 0; + + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + dele++; + } else { + allo++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele); + fs->stats_p_allocated -= allo; + fs->stats_p_deleted -= dele; + return res; +} + +// Finds block candidates to erase +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidates, + int *candidate_count, + char fs_crammed) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + + // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score + int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t))); + *candidate_count = 0; + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + // divide up work area into block indices and scores + spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; + s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); + + // align cand_scores on s32_t boundary + cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); + + *block_candidates = cand_blocks; + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t used_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + // when a free entry is encountered, scan logic ensures that all following entries are free also + res = 1; // kill object lu loop + break; + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else { + used_pages_in_block++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + // calculate score and insert into candidate table + // stoneage sort, but probably not so many blocks + if (res == SPIFFS_OK /*&& deleted_pages_in_block > 0*/) { + // read erase count + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, cur_block), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + spiffs_obj_id erase_age; + if (fs->max_erase_count > erase_count) { + erase_age = fs->max_erase_count - erase_count; + } else { + erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count); + } + + s32_t score = + deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET + + used_pages_in_block * SPIFFS_GC_HEUR_W_USED + + erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); + int cand_ix = 0; + SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); + while (cand_ix < max_candidates) { + if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } else if (cand_scores[cand_ix] < score) { + int reorder_cand_ix = max_candidates - 2; + while (reorder_cand_ix >= cand_ix) { + cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix]; + cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix]; + reorder_cand_ix--; + } + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } + cand_ix++; + } + (*candidate_count)++; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + return res; +} + +typedef enum { + FIND_OBJ_DATA, + MOVE_OBJ_DATA, + MOVE_OBJ_IX, + FINISHED +} spiffs_gc_clean_state; + +typedef struct { + spiffs_gc_clean_state state; + spiffs_obj_id cur_obj_id; + spiffs_span_ix cur_objix_spix; + spiffs_page_ix cur_objix_pix; + spiffs_page_ix cur_data_pix; + int stored_scan_entry_index; + u8_t obj_id_found; +} spiffs_gc; + +// Empties given block by moving all data into free pages of another block +// Strategy: +// loop: +// scan object lookup for object data pages +// for first found id, check spix and load corresponding object index page to memory +// push object scan lookup entry index +// rescan object lookup, find data pages with same id and referenced by same object index +// move data page, update object index in memory +// when reached end of lookup, store updated object index +// pop object scan lookup entry index +// repeat loop until end of object lookup +// scan object lookup again for remaining object index pages, move to new page in other block +// +s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + // this is the global localizer being pushed and popped + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_gc gc; // our stack frame/state + spiffs_page_ix cur_pix = 0; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix); + + memset(&gc, 0, sizeof(spiffs_gc)); + gc.state = FIND_OBJ_DATA; + + if (fs->free_cursor_block_ix == bix) { + // move free cursor to next block, cannot use free pages from the block we want to clean + fs->free_cursor_block_ix = (bix+1)%fs->block_count; + fs->free_cursor_obj_lu_entry = 0; + SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix); + } + + while (res == SPIFFS_OK && gc.state != FINISHED) { + SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry); + gc.obj_id_found = 0; // reset (to no found data page) + + // scan through lookup pages + int obj_lookup_page = cur_entry / entries_per_page; + u8_t scan = 1; + // check each object lookup page + while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each object lookup entry + while (scan && res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); + + // act upon object id depending on gc state + switch (gc.state) { + case FIND_OBJ_DATA: + // find a data page + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { + // found a data page, stop scanning and handle in switch case below + SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id); + gc.obj_id_found = 1; + gc.cur_obj_id = obj_id; + gc.cur_data_pix = cur_pix; + scan = 0; + } + break; + case MOVE_OBJ_DATA: + // evacuate found data pages for corresponding object index we have in memory, + // update memory representation + if (obj_id == gc.cur_obj_id) { + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { + SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); + } else { + spiffs_page_ix new_data_pix; + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); + SPIFFS_CHECK_RES(res); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + new_data_pix = SPIFFS_OBJ_ID_FREE; + } + // update memory representation of object index page with new data page + if (gc.cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } + } + } + break; + case MOVE_OBJ_IX: + // find and evacuate object index pages + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + // found an index object id + spiffs_page_header p_hdr; + spiffs_page_ix new_pix; + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr, + SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + if (res == SPIFFS_OK) { + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); + } + } + SPIFFS_CHECK_RES(res); + } + break; + default: + scan = 0; + break; + } // switch gc state + cur_entry++; + } // per entry + obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop + } // per object lookup page + if (res != SPIFFS_OK) break; + + // state finalization and switch + switch (gc.state) { + case FIND_OBJ_DATA: + if (gc.obj_id_found) { + // handle found data page - + // find out corresponding obj ix page and load it to memory + spiffs_page_header p_hdr; + spiffs_page_ix objix_pix; + gc.stored_scan_entry_index = cur_entry; // push cursor + cur_entry = 0; // restart scan from start + gc.state = MOVE_OBJ_DATA; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); + SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix); + res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // on borked systems we might get an ERR_NOT_FOUND here - + // this is handled by simply deleting the page as it is not referenced + // from anywhere + SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix); + res = spiffs_page_delete(fs, gc.cur_data_pix); + SPIFFS_CHECK_RES(res); + // then we restore states and continue scanning for data pages + cur_entry = gc.stored_scan_entry_index; // pop cursor + gc.state = FIND_OBJ_DATA; + break; // done + } + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + // cannot allow a gc if the presumed index in fact is no index, a + // check must run or lot of data may be lost + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); + gc.cur_objix_pix = objix_pix; + } else { + // no more data pages found, passed thru all block, start evacuating object indices + gc.state = MOVE_OBJ_IX; + cur_entry = 0; // restart entry scan index + } + break; + case MOVE_OBJ_DATA: { + // store modified objix (hdr) page residing in memory now that all + // data pages belonging to this object index and residing in the block + // we want to evacuate + spiffs_page_ix new_objix_pix; + gc.state = FIND_OBJ_DATA; + cur_entry = gc.stored_scan_entry_index; // pop cursor + if (gc.cur_objix_spix == 0) { + // store object index header page + res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0); + SPIFFS_CHECK_RES(res); + } else { + // store object index page + res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + break; + case MOVE_OBJ_IX: + // scanned thru all block, no more object indices found - our work here is done + gc.state = FINISHED; + break; + default: + cur_entry = 0; + break; + } // switch gc.state + SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state); + } // while state != FINISHED + + + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/components/mkspiffs/src/spiffs/spiffs_hydrogen.c b/components/mkspiffs/src/spiffs/spiffs_hydrogen.c new file mode 100644 index 0000000..9ff3e7a --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_hydrogen.c @@ -0,0 +1,1405 @@ +/* + * spiffs_hydrogen.c + * + * Created on: Jun 16, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0) +#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0) +#else +#define SPIFFS_FH_OFFS(fs, fh) (fh) +#define SPIFFS_FH_UNOFFS(fs, fh) (fh) +#endif + +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh); +#endif + +#if SPIFFS_BUFFER_HELP +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) { + return num_descs * sizeof(spiffs_fd); +} +#if SPIFFS_CACHE +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages) { + return sizeof(spiffs_cache) + num_pages * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)); +} +#endif +#endif + +u8_t SPIFFS_mounted(spiffs *fs) { + return SPIFFS_CHECK_MOUNT(fs); +} + +s32_t SPIFFS_format(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + if (SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_MOUNTED; + return -1; + } + + s32_t res; + SPIFFS_LOCK(fs); + + spiffs_block_ix bix = 0; + while (bix < fs->block_count) { + fs->max_erase_count = 0; + res = spiffs_erase_block(fs, bix); + if (res != SPIFFS_OK) { + res = SPIFFS_ERR_ERASE_FAIL; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + bix++; + } + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_probe_fs(spiffs_config *config) { + s32_t res = spiffs_probe(config); + return res; +} + +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f) { + void *user_data; + SPIFFS_LOCK(fs); + user_data = fs->user_data; + memset(fs, 0, sizeof(spiffs)); + memcpy(&fs->cfg, config, sizeof(spiffs_config)); + fs->user_data = user_data; + fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); + fs->work = &work[0]; + fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; + memset(fd_space, 0, fd_space_size); + // align fd_space pointer to pointer size byte boundary + u8_t ptr_size = sizeof(void*); + u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1); + if (addr_lsb) { + fd_space += (ptr_size-addr_lsb); + fd_space_size -= (ptr_size-addr_lsb); + } + fs->fd_space = fd_space; + fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); + + // align cache pointer to 4 byte boundary + addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1); + if (addr_lsb) { + u8_t *cache_8 = (u8_t *)cache; + cache_8 += (ptr_size-addr_lsb); + cache = cache_8; + cache_size -= (ptr_size-addr_lsb); + } + if (cache_size & (ptr_size-1)) { + cache_size -= (cache_size & (ptr_size-1)); + } + +#if SPIFFS_CACHE + fs->cache = cache; + fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size; + spiffs_cache_init(fs); +#endif + + s32_t res; + +#if SPIFFS_USE_MAGIC + res = SPIFFS_CHECK_MAGIC_POSSIBLE(fs) ? SPIFFS_OK : SPIFFS_ERR_MAGIC_NOT_POSSIBLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + fs->config_magic = SPIFFS_CONFIG_MAGIC; + + res = spiffs_obj_lu_scan(fs); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_DBG("page index byte len: "_SPIPRIi"\n", (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)); + SPIFFS_DBG("object lookup pages: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_LOOKUP_PAGES(fs)); + SPIFFS_DBG("page pages per block: "_SPIPRIi"\n", (u32_t)SPIFFS_PAGES_PER_BLOCK(fs)); + SPIFFS_DBG("page header length: "_SPIPRIi"\n", (u32_t)sizeof(spiffs_page_header)); + SPIFFS_DBG("object header index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_HDR_IX_LEN(fs)); + SPIFFS_DBG("object index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_IX_LEN(fs)); + SPIFFS_DBG("available file descriptors: "_SPIPRIi"\n", (u32_t)fs->fd_count); + SPIFFS_DBG("free blocks: "_SPIPRIi"\n", (u32_t)fs->free_blocks); + + fs->check_cb_f = check_cb_f; + + fs->mounted = 1; + + SPIFFS_UNLOCK(fs); + + return 0; +} + +void SPIFFS_unmount(spiffs *fs) { + if (!SPIFFS_CHECK_CFG(fs) || !SPIFFS_CHECK_MOUNT(fs)) return; + SPIFFS_LOCK(fs); + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0) { +#if SPIFFS_CACHE + (void)spiffs_fflush_cache(fs, cur_fd->file_nbr); +#endif + spiffs_fd_return(fs, cur_fd->file_nbr); + } + } + fs->mounted = 0; + + SPIFFS_UNLOCK(fs); +} + +s32_t SPIFFS_errno(spiffs *fs) { + return fs->err_code; +} + +void SPIFFS_clearerr(spiffs *fs) { + fs->err_code = SPIFFS_OK; +} + +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; (void)mode; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + spiffs_obj_id obj_id; + s32_t res; + + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (const u8_t*)path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) { + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + +#if SPIFFS_READ_ONLY + // not valid flags in read only mode + flags &= ~(SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC); +#endif // SPIFFS_READ_ONLY + + s32_t res = spiffs_fd_find_new(fs, &fd, path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if ((flags & SPIFFS_O_CREAT) == 0) { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (res == SPIFFS_OK && + (flags & (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) == (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) { + // creat and excl and file exists - fail + res = SPIFFS_ERR_FILE_EXISTS; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((flags & SPIFFS_O_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { +#if !SPIFFS_READ_ONLY + spiffs_obj_id obj_id; + // no need to enter conflicting name here, already looked for it above + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, &pix); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + flags &= ~SPIFFS_O_TRUNC; +#endif // !SPIFFS_READ_ONLY + } else { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, e->pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (SPIFFS_IS_LOOKUP_PAGE(fs, page_ix)) { + res = SPIFFS_ERR_NOT_A_FILE; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_open_by_page(fs, page_ix, fd, flags, mode); + if (res == SPIFFS_ERR_IS_FREE || + res == SPIFFS_ERR_DELETED || + res == SPIFFS_ERR_NOT_FINALIZED || + res == SPIFFS_ERR_NOT_INDEX || + res == SPIFFS_ERR_INDEX_SPAN_MISMATCH) { + res = SPIFFS_ERR_NOT_A_FILE; + } + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +static s32_t spiffs_hydro_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_RDONLY) == 0) { + res = SPIFFS_ERR_NOT_READABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (fd->size == SPIFFS_UNDEFINED_LEN && len > 0) { + // special case for zero sized files + res = SPIFFS_ERR_END_OF_OBJECT; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + if (fd->fdoffset + len >= fd->size) { + // reading beyond file size + s32_t avail = fd->size - fd->fdoffset; + if (avail <= 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT); + } + res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + fd->fdoffset += avail; + SPIFFS_UNLOCK(fs); + return avail; + } else { + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + len = avail; + } + } else { + // reading within file size + res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return len; +} + +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + s32_t res = spiffs_hydro_read(fs, fh, buf, len); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + res = 0; + } + return res; +} + + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) { + (void)fs; + s32_t res = SPIFFS_OK; + s32_t remaining = len; + if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) { + s32_t m_len = MIN((s32_t)(fd->size - offset), len); + res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len); + SPIFFS_CHECK_RES(res); + remaining -= m_len; + u8_t *buf_8 = (u8_t *)buf; + buf_8 += m_len; + buf = buf_8; + offset += m_len; + } + if (remaining > 0) { + res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining); + SPIFFS_CHECK_RES(res); + } + return len; + +} +#endif // !SPIFFS_READ_ONLY + +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)buf; (void)len; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + u32_t offset; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((fd->flags & SPIFFS_O_APPEND)) { + fd->fdoffset = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + } + + offset = fd->fdoffset; + +#if SPIFFS_CACHE_WR + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } +#endif + if (fd->flags & SPIFFS_O_APPEND) { + if (fd->size == SPIFFS_UNDEFINED_LEN) { + offset = 0; + } else { + offset = fd->size; + } +#if SPIFFS_CACHE_WR + if (fd->cache_page) { + offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size); + } +#endif + } + +#if SPIFFS_CACHE_WR + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (len < (s32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + // small write, try to cache it + u8_t alloc_cpage = 1; + if (fd->cache_page) { + // have a cached page for this fd already, check cache page boundaries + if (offset < fd->cache_page->offset || // writing before cache + offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache + offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page + { + // boundary violation, write back cache first and allocate new + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", boundary viol, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // writing within cache + alloc_cpage = 0; + } + } + + if (alloc_cpage) { + fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd); + if (fd->cache_page) { + fd->cache_page->offset = offset; + fd->cache_page->size = 0; + SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id); + } + } + + if (fd->cache_page) { + u32_t offset_in_cpage = offset - fd->cache_page->offset; + SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", offs "_SPIPRIi":"_SPIPRIi" len "_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, + offset, offset_in_cpage, len); + spiffs_cache *cache = spiffs_get_cache(fs); + u8_t *cpage_data = spiffs_get_cache_page(fs, cache, fd->cache_page->ix); + memcpy(&cpage_data[offset_in_cpage], buf, len); + fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return len; + } else { + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return res; + } + } else { + // big write, no need to cache it - but first check if there is a cached write already + if (fd->cache_page) { + // write back cache first + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", big write, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + // data written below + } + } + } +#endif + + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + s32_t fileSize = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + + switch (whence) { + case SPIFFS_SEEK_CUR: + offs = fd->fdoffset+offs; + break; + case SPIFFS_SEEK_END: + offs = fileSize + offs; + break; + } + + if ((offs > fileSize)) { + fd->fdoffset = fileSize; + res = SPIFFS_ERR_END_OF_OBJECT; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (fd->cursor_objix_spix != objix_spix) { + spiffs_page_ix pix; + res = spiffs_obj_lu_find_id_and_span( + fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->cursor_objix_spix = objix_spix; + fd->cursor_objix_pix = pix; + } + fd->fdoffset = offs; + + SPIFFS_UNLOCK(fs); + + return offs; +} + +s32_t SPIFFS_remove(spiffs *fs, const char *path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + s32_t res; + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0,0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_truncate(fd, 0, 1); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_cache_fd_release(fs, fd->cache_page); +#endif + + res = spiffs_object_truncate(fd, 0, 1); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) { + (void)fh; + spiffs_page_object_ix_header objix_hdr; + spiffs_obj_id obj_id; + s32_t res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_API_CHECK_RES(fs, res); + + u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , pix)) + + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_obj_id); + res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, fh, + obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id); + SPIFFS_API_CHECK_RES(fs, res); + + s->obj_id = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + s->type = objix_hdr.type; + s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + s->pix = pix; + strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + memcpy(s->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + + return res; +} + +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_page_ix pix; + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_stat_pix(fs, pix, 0, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + res = spiffs_stat_pix(fs, fd->objix_hdr_pix, fh, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +// Checks if there are any cached writes for the object id associated with +// given filehandle. If so, these writes are flushed. +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { + (void)fs; + (void)fh; + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES(fs, res); + + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } + if (fd->cache_page) { + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", flush, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + if (res < SPIFFS_OK) { + fs->err_code = res; + } + spiffs_cache_fd_release(fs, fd->cache_page); + } + } +#endif + + return res; +} +#endif + +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { + (void)fh; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + SPIFFS_LOCK(fs); + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs,res); + SPIFFS_UNLOCK(fs); +#endif + + return res; +} + +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + + s32_t res = SPIFFS_OK; + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); +#if SPIFFS_CACHE + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + res = spiffs_fd_return(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_rename(spiffs *fs, const char *old_path, const char *new_path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)old_path; (void)new_path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(new_path) > SPIFFS_OBJ_NAME_LEN - 1 || + strlen(old_path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_page_ix pix_old, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)old_path, &pix_old); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)new_path, &pix_dummy); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + } else if (res == SPIFFS_OK) { + res = SPIFFS_ERR_CONFLICTING_NAME; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix_old, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (const u8_t*)new_path, + 0, 0, &pix_dummy); +#if SPIFFS_TEMPORAL_FD_CACHE + if (res == SPIFFS_OK) { + spiffs_fd_temporal_cache_rehash(fs, old_path, new_path); + } +#endif + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_OBJ_META_LEN +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)name; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_page_ix pix, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)name, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_fd *fd; + spiffs_page_ix pix_dummy; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} +#endif // SPIFFS_OBJ_META_LEN + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) { + (void)name; + + if (!SPIFFS_CHECK_CFG((fs))) { + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; + return 0; + } + + if (!SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + + d->fs = fs; + d->block = 0; + d->entry = 0; + return d; +} + +static s32_t spiffs_read_dir_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + if (res != SPIFFS_OK) return res; + if ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && + objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + struct spiffs_dirent *e = (struct spiffs_dirent*)user_var_p; + e->obj_id = obj_id; + strcpy((char *)e->name, (char *)objix_hdr.name); + e->type = objix_hdr.type; + e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + e->pix = pix; +#if SPIFFS_OBJ_META_LEN + memcpy(e->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + return SPIFFS_OK; + } + return SPIFFS_VIS_COUNTINUE; +} + +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { + if (!SPIFFS_CHECK_MOUNT(d->fs)) { + d->fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + SPIFFS_LOCK(d->fs); + + spiffs_block_ix bix; + int entry; + s32_t res; + struct spiffs_dirent *ret = 0; + + res = spiffs_obj_lu_find_entry_visitor(d->fs, + d->block, + d->entry, + SPIFFS_VIS_NO_WRAP, + 0, + spiffs_read_dir_v, + 0, + e, + &bix, + &entry); + if (res == SPIFFS_OK) { + d->block = bix; + d->entry = entry + 1; + e->obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + ret = e; + } else { + d->fs->err_code = res; + } + SPIFFS_UNLOCK(d->fs); + return ret; +} + +s32_t SPIFFS_closedir(spiffs_DIR *d) { + SPIFFS_API_CHECK_CFG(d->fs); + SPIFFS_API_CHECK_MOUNT(d->fs); + return 0; +} + +s32_t SPIFFS_check(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_lookup_consistency_check(fs, 0); + + res = spiffs_object_index_consistency_check(fs); + + res = spiffs_page_consistency_check(fs); + + res = spiffs_obj_lu_scan(fs); + + SPIFFS_UNLOCK(fs); + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs); + u32_t blocks = fs->block_count; + u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs); + u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs); + u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page + + if (total) { + *total = total_data_pages * data_page_size; + } + + if (used) { + *used = fs->stats_p_allocated * data_page_size; + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) { +#if SPIFFS_READ_ONLY + (void)fs; (void)max_free_pages; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_quick(fs, max_free_pages); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + + +s32_t SPIFFS_gc(spiffs *fs, u32_t size) { +#if SPIFFS_READ_ONLY + (void)fs; (void)size; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_check(fs, size); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = (fd->fdoffset >= (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size)); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = fd->fdoffset; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func) { + SPIFFS_LOCK(fs); + fs->file_cb_f = cb_func; + SPIFFS_UNLOCK(fs); + return 0; +} + +#if SPIFFS_IX_MAP + +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_MAPPED); + } + + map->map_buf = map_buf; + map->offset = offset; + // nb: spix range includes last + map->start_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + map->end_spix = (offset + len) / SPIFFS_DATA_PAGE_SIZE(fs); + memset(map_buf, 0, sizeof(spiffs_page_ix) * (map->end_spix - map->start_spix + 1)); + fd->ix_map = map; + + // scan for pixes + res = spiffs_populate_ix_map(fs, fd, 0, map->end_spix - map->start_spix + 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + fd->ix_map = 0; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offset) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + spiffs_ix_map *map = fd->ix_map; + + s32_t spix_diff = offset / SPIFFS_DATA_PAGE_SIZE(fs) - map->start_spix; + map->offset = offset; + + // move existing pixes if within map offs + if (spix_diff != 0) { + // move vector + int i; + const s32_t vec_len = map->end_spix - map->start_spix + 1; // spix range includes last + map->start_spix += spix_diff; + map->end_spix += spix_diff; + if (spix_diff >= vec_len) { + // moving beyond range + memset(&map->map_buf, 0, vec_len * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else if (spix_diff > 0) { + // diff positive + for (i = 0; i < vec_len - spix_diff; i++) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[vec_len - spix_diff], 0, spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, vec_len - spix_diff, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // diff negative + for (i = vec_len - 1; i >= -spix_diff; i--) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[0], 0, -spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, -spix_diff - 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes) { + SPIFFS_API_CHECK_CFG(fs); + // always add one extra page, the offset might change to the middle of a page + return (bytes + SPIFFS_DATA_PAGE_SIZE(fs) ) / SPIFFS_DATA_PAGE_SIZE(fs); +} + +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries) { + SPIFFS_API_CHECK_CFG(fs); + return map_page_ix_entries * SPIFFS_DATA_PAGE_SIZE(fs); +} + +#endif // SPIFFS_IX_MAP + +#if SPIFFS_TEST_VISUALISATION +s32_t SPIFFS_vis(spiffs *fs) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_block_ix bix = 0; + + while (bix < fs->block_count) { + // check each object lookup page + int obj_lookup_page = 0; + int cur_entry = 0; + + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (cur_entry == 0) { + spiffs_printf(_SPIPRIbl" ", bix); + } else if ((cur_entry & 0x3f) == 0) { + spiffs_printf(" "); + } + if (obj_id == SPIFFS_OBJ_ID_FREE) { + spiffs_printf(SPIFFS_TEST_VIS_FREE_STR); + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + spiffs_printf(SPIFFS_TEST_VIS_DELE_STR); + } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){ + spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id)); + } else { + spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id)); + } + cur_entry++; + if ((cur_entry & 0x3f) == 0) { + spiffs_printf("\n"); + } + } // per entry + obj_lookup_page++; + } // per object lookup page + + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + if (erase_count != (spiffs_obj_id)-1) { + spiffs_printf("\tera_cnt: "_SPIPRIi"\n", erase_count); + } else { + spiffs_printf("\tera_cnt: N/A\n"); + } + + bix++; + } // per block + + spiffs_printf("era_cnt_max: "_SPIPRIi"\n", fs->max_erase_count); + spiffs_printf("last_errno: "_SPIPRIi"\n", fs->err_code); + spiffs_printf("blocks: "_SPIPRIi"\n", fs->block_count); + spiffs_printf("free_blocks: "_SPIPRIi"\n", fs->free_blocks); + spiffs_printf("page_alloc: "_SPIPRIi"\n", fs->stats_p_allocated); + spiffs_printf("page_delet: "_SPIPRIi"\n", fs->stats_p_deleted); + SPIFFS_UNLOCK(fs); + u32_t total, used; + SPIFFS_info(fs, &total, &used); + spiffs_printf("used: "_SPIPRIi" of "_SPIPRIi"\n", used, total); + return res; +} +#endif diff --git a/components/mkspiffs/src/spiffs/spiffs_nucleus.c b/components/mkspiffs/src/spiffs/spiffs_nucleus.c new file mode 100644 index 0000000..44ba711 --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_nucleus.c @@ -0,0 +1,2327 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_REF_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_REF_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_REF_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_DATA(ph, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, spix); +#endif + return res; +} + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix); +#endif + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_CACHE + +s32_t spiffs_phys_rd( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *dst) { + return SPIFFS_HAL_READ(fs, addr, len, dst); +} + +s32_t spiffs_phys_wr( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *src) { + return SPIFFS_HAL_WRITE(fs, addr, len, src); +} + +#endif + +#if !SPIFFS_READ_ONLY +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len) { + (void)fh; + s32_t res; + u8_t b[SPIFFS_COPY_BUFFER_STACK]; + while (len > 0) { + u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, fh, dst, chunk_size, b); + SPIFFS_CHECK_RES(res); + len -= chunk_size; + src += chunk_size; + dst += chunk_size; + } + return SPIFFS_OK; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id with visitor. +// Iterate over object lookup pages in each block until a given object id entry is found. +// When found, the visitor function is called with block index, entry index and user data. +// If visitor returns SPIFFS_VIS_CONTINUE, the search goes on. Otherwise, the search will be +// ended and visitor's return code is returned to caller. +// If no visitor is given (0) the search returns on first entry with matching object id. +// If no match is found in all look up, SPIFFS_VIS_END is returned. +// @param fs the file system +// @param starting_block the starting block to start search in +// @param starting_lu_entry the look up index entry to start search in +// @param flags ored combination of SPIFFS_VIS_CHECK_ID, SPIFFS_VIS_CHECK_PH, +// SPIFFS_VIS_NO_WRAP +// @param obj_id argument object id +// @param v visitor callback function +// @param user_const_p any const pointer, passed to the callback visitor function +// @param user_var_p any pointer, passed to the callback visitor function +// @param block_ix reported block index where match was found +// @param lu_entry reported look up index where match was found +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = SPIFFS_OK; + s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs); + spiffs_block_ix cur_block = starting_block; + u32_t cur_block_addr = starting_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = starting_lu_entry; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // wrap initial + if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { + cur_entry = 0; + cur_block++; + cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } + + // check each block + while (res == SPIFFS_OK && entry_count > 0) { + int obj_lookup_page = cur_entry / entries_per_page; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages + cur_entry < (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page + { + if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) { + if (block_ix) *block_ix = cur_block; + if (lu_entry) *lu_entry = cur_entry; + if (v) { + res = v( + fs, + (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset], + cur_block, + cur_entry, + user_const_p, + user_var_p); + if (res == SPIFFS_VIS_COUNTINUE || res == SPIFFS_VIS_COUNTINUE_RELOAD) { + if (res == SPIFFS_VIS_COUNTINUE_RELOAD) { + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } + res = SPIFFS_OK; + cur_entry++; + entry_count--; + continue; + } else { + return res; + } + } else { + return SPIFFS_OK; + } + } + entry_count--; + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } // per block + + SPIFFS_CHECK_RES(res); + + return SPIFFS_VIS_END; +} + +#if !SPIFFS_READ_ONLY +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, bix); + s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + // here we ignore res, just try erasing the block + while (size > 0) { + SPIFFS_DBG("erase "_SPIPRIad":"_SPIPRIi"\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + SPIFFS_HAL_ERASE(fs, addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + + addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs); + size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs); + } + fs->free_blocks++; + + // register erase count for this block + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&fs->max_erase_count); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_USE_MAGIC + // finally, write magic + spiffs_obj_id magic = SPIFFS_MAGIC(fs, bix); + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_MAGIC_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&magic); + SPIFFS_CHECK_RES(res); +#endif + + fs->max_erase_count++; + if (fs->max_erase_count == SPIFFS_OBJ_ID_IX_FLAG) { + fs->max_erase_count = 0; + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +s32_t spiffs_probe( + spiffs_config *cfg) { + s32_t res; + u32_t paddr; + spiffs dummy_fs; // create a dummy fs struct just to be able to use macros + memcpy(&dummy_fs.cfg, cfg, sizeof(spiffs_config)); + dummy_fs.block_count = 0; + + // Read three magics, as one block may be in an aborted erase state. + // At least two of these must contain magic and be in decreasing order. + spiffs_obj_id magic[3]; + spiffs_obj_id bix_count[3]; + + spiffs_block_ix bix; + for (bix = 0; bix < 3; bix++) { + paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix); +#if SPIFFS_HAL_CALLBACK_EXTRA + // not any proper fs to report here, so callback with null + // (cross fingers that no-one gets angry) + res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#else + res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#endif + bix_count[bix] = magic[bix] ^ SPIFFS_MAGIC(&dummy_fs, 0); + SPIFFS_CHECK_RES(res); + } + + // check that we have sane number of blocks + if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS; + // check that the order is correct, take aborted erases in calculation + // first block aborted erase + if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) { + return (bix_count[1]+1) * cfg->log_block_size; + } + // second block aborted erase + if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) { + return bix_count[0] * cfg->log_block_size; + } + // third block aborted erase + if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) { + return bix_count[0] * cfg->log_block_size; + } + // no block has aborted erase + if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) { + return bix_count[0] * cfg->log_block_size; + } + + return SPIFFS_ERR_PROBE_NOT_A_FS; +} +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + + +static s32_t spiffs_obj_lu_scan_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)bix; + (void)user_const_p; + (void)user_var_p; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + if (ix_entry == 0) { + fs->free_blocks++; + // todo optimize further, return SPIFFS_NEXT_BLOCK + } + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + fs->stats_p_deleted++; + } else { + fs->stats_p_allocated++; + } + + return SPIFFS_VIS_COUNTINUE; +} + + +// Scans thru all obj lu and counts free, deleted and used pages +// Find the maximum block erase count +// Checks magic if enabled +s32_t spiffs_obj_lu_scan( + spiffs *fs) { + s32_t res; + spiffs_block_ix bix; + int entry; +#if SPIFFS_USE_MAGIC + spiffs_block_ix unerased_bix = (spiffs_block_ix)-1; +#endif + + // find out erase count + // if enabled, check magic + bix = 0; + spiffs_obj_id erase_count_final; + spiffs_obj_id erase_count_min = SPIFFS_OBJ_ID_FREE; + spiffs_obj_id erase_count_max = 0; + while (bix < fs->block_count) { +#if SPIFFS_USE_MAGIC + spiffs_obj_id magic; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_MAGIC_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&magic); + + SPIFFS_CHECK_RES(res); + if (magic != SPIFFS_MAGIC(fs, bix)) { + if (unerased_bix == (spiffs_block_ix)-1) { + // allow one unerased block as it might be powered down during an erase + unerased_bix = bix; + } else { + // more than one unerased block, bail out + SPIFFS_CHECK_RES(SPIFFS_ERR_NOT_A_FS); + } + } +#endif + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + if (erase_count != SPIFFS_OBJ_ID_FREE) { + erase_count_min = MIN(erase_count_min, erase_count); + erase_count_max = MAX(erase_count_max, erase_count); + } + bix++; + } + + if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJ_ID_FREE) { + // clean system, set counter to zero + erase_count_final = 0; + } else if (erase_count_max - erase_count_min > (SPIFFS_OBJ_ID_FREE)/2) { + // wrap, take min + erase_count_final = erase_count_min+1; + } else { + erase_count_final = erase_count_max+1; + } + + fs->max_erase_count = erase_count_final; + +#if SPIFFS_USE_MAGIC + if (unerased_bix != (spiffs_block_ix)-1) { + // found one unerased block, remedy + SPIFFS_DBG("mount: erase block "_SPIPRIbl"\n", bix); +#if SPIFFS_READ_ONLY + res = SPIFFS_ERR_RO_ABORTED_OPERATION; +#else + res = spiffs_erase_block(fs, unerased_bix); +#endif // SPIFFS_READ_ONLY + SPIFFS_CHECK_RES(res); + } +#endif + + // count blocks + + fs->free_blocks = 0; + fs->stats_p_allocated = 0; + fs->stats_p_deleted = 0; + + res = spiffs_obj_lu_find_entry_visitor(fs, + 0, + 0, + 0, + 0, + spiffs_obj_lu_scan_v, + 0, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + SPIFFS_CHECK_RES(res); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Find free object lookup entry +// Iterate over object lookup pages in each block until a free object id entry is found +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res; + if (!fs->cleaning && fs->free_blocks < 2) { + res = spiffs_gc_quick(fs, 0); + if (res == SPIFFS_ERR_NO_DELETED_BLOCKS) { + res = SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + if (fs->free_blocks < 2) { + return SPIFFS_ERR_FULL; + } + } + res = spiffs_obj_lu_find_id(fs, starting_block, starting_lu_entry, + SPIFFS_OBJ_ID_FREE, block_ix, lu_entry); + if (res == SPIFFS_OK) { + fs->free_cursor_block_ix = *block_ix; + fs->free_cursor_obj_lu_entry = (*lu_entry) + 1; + if (*lu_entry == 0) { + fs->free_blocks--; + } + } + if (res == SPIFFS_ERR_FULL) { + SPIFFS_DBG("fs full\n"); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = spiffs_obj_lu_find_entry_visitor( + fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, lu_entry); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + return res; +} + + +static s32_t spiffs_obj_lu_find_id_and_span_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + s32_t res; + spiffs_page_header ph; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + if (ph.obj_id == obj_id && + ph.span_ix == *((spiffs_span_ix*)user_var_p) && + (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET && + !((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (ph.flags & SPIFFS_PH_FLAG_IXDELE) == 0 && ph.span_ix == 0) && + (user_const_p == 0 || *((const spiffs_page_ix*)user_const_p) != pix)) { + return SPIFFS_OK; + } else { + return SPIFFS_VIS_COUNTINUE; + } +} + +// Find object lookup entry containing given id and span index +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_ID, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +// Find object lookup entry containing given id and span index in page headers only +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_PH, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if SPIFFS_IX_MAP + +// update index map of given fd with given object index data +static void spiffs_update_ix_map(spiffs *fs, + spiffs_fd *fd, spiffs_span_ix objix_spix, spiffs_page_object_ix *objix) { +#if SPIFFS_SINGLETON + (void)fs; +#endif + spiffs_ix_map *map = fd->ix_map; + spiffs_span_ix map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix); + spiffs_span_ix map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->end_spix); + + // check if updated ix is within map range + if (objix_spix < map_objix_start_spix || objix_spix > map_objix_end_spix) { + return; + } + + // update memory mapped page index buffer to new pages + + // get range of updated object index map data span indices + spiffs_span_ix objix_data_spix_start = + SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, objix_spix); + spiffs_span_ix objix_data_spix_end = objix_data_spix_start + + (objix_spix == 0 ? SPIFFS_OBJ_HDR_IX_LEN(fs) : SPIFFS_OBJ_IX_LEN(fs)); + + // calc union of object index range and index map range array + spiffs_span_ix map_spix = MAX(map->start_spix, objix_data_spix_start); + spiffs_span_ix map_spix_end = MIN(map->end_spix + 1, objix_data_spix_end); + + while (map_spix < map_spix_end) { + spiffs_page_ix objix_data_pix; + if (objix_spix == 0) { + // get data page from object index header page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix_header)))[map_spix]; + } else { + // get data page from object index page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, map_spix)]; + } + + if (objix_data_pix == (spiffs_page_ix)-1) { + // reached end of object, abort + break; + } + + map->map_buf[map_spix - map->start_spix] = objix_data_pix; + SPIFFS_DBG("map "_SPIPRIid":"_SPIPRIsp" ("_SPIPRIsp"--"_SPIPRIsp") objix.spix:"_SPIPRIsp" to pix "_SPIPRIpg"\n", + fd->obj_id, map_spix - map->start_spix, + map->start_spix, map->end_spix, + objix->p_hdr.span_ix, + objix_data_pix); + + map_spix++; + } +} + +typedef struct { + spiffs_fd *fd; + u32_t remaining_objix_pages_to_visit; + spiffs_span_ix map_objix_start_spix; + spiffs_span_ix map_objix_end_spix; +} spiffs_ix_map_populate_state; + +static s32_t spiffs_populate_ix_map_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_ix_map_populate_state *state = (spiffs_ix_map_populate_state *)user_var_p; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + + // load header to check it + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix), (u8_t *)objix); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, obj_id, objix->p_hdr.span_ix); + + // check if hdr is ok, and if objix range overlap with ix map range + if ((objix->p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE) && + objix->p_hdr.span_ix >= state->map_objix_start_spix && + objix->p_hdr.span_ix <= state->map_objix_end_spix) { + // ok, load rest of object index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + sizeof(spiffs_page_object_ix), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix), + (u8_t *)objix + sizeof(spiffs_page_object_ix)); + SPIFFS_CHECK_RES(res); + + spiffs_update_ix_map(fs, state->fd, objix->p_hdr.span_ix, objix); + + state->remaining_objix_pages_to_visit--; + SPIFFS_DBG("map "_SPIPRIid" ("_SPIPRIsp"--"_SPIPRIsp") remaining objix pages "_SPIPRIi"\n", + state->fd->obj_id, + state->fd->ix_map->start_spix, state->fd->ix_map->end_spix, + state->remaining_objix_pages_to_visit); + } + + if (res == SPIFFS_OK) { + res = state->remaining_objix_pages_to_visit ? SPIFFS_VIS_COUNTINUE : SPIFFS_VIS_END; + } + return res; +} + +// populates index map, from vector entry start to vector entry end, inclusive +s32_t spiffs_populate_ix_map(spiffs *fs, spiffs_fd *fd, u32_t vec_entry_start, u32_t vec_entry_end) { + s32_t res; + spiffs_ix_map *map = fd->ix_map; + spiffs_ix_map_populate_state state; + vec_entry_start = MIN((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_start); + vec_entry_end = MAX((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_end); + if (vec_entry_start > vec_entry_end) { + return SPIFFS_ERR_IX_MAP_BAD_RANGE; + } + state.map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_start); + state.map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_end); + state.remaining_objix_pages_to_visit = + state.map_objix_end_spix - state.map_objix_start_spix + 1; + state.fd = fd; + + res = spiffs_obj_lu_find_entry_visitor( + fs, + SPIFFS_BLOCK_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_VIS_CHECK_ID, + fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + spiffs_populate_ix_map_v, + 0, + &state, + 0, + 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + return res; +} + +#endif + + +#if !SPIFFS_READ_ONLY +// Allocates a free defined page with given obj_id +// Occupies object lookup entry and page +// data may be NULL; where only page header is stored, len and page_offs is ignored +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + int entry; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write page header + ph->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_header), (u8_t*)ph); + SPIFFS_CHECK_RES(res); + + // write page data + if (data) { + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0,SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + sizeof(spiffs_page_header) + page_offs, len, data); + SPIFFS_CHECK_RES(res); + } + + // finalize header if necessary + if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) { + ph->flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&ph->flags); + SPIFFS_CHECK_RES(res); + } + + // return written page + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page. +// If page data is null, provided header is used for metainfo and page data is physically copied. +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix) { + s32_t res; + u8_t was_final = 0; + spiffs_page_header *p_hdr; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + if (dst_pix) *dst_pix = free_pix; + + p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr; + if (page_data) { + // got page data + was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0; + // write unfinalized page + p_hdr->flags |= SPIFFS_PH_FLAG_FINAL; + p_hdr->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data); + } else { + // copy page data + res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs)); + } + SPIFFS_CHECK_RES(res); + + // mark entry in destination object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + if (was_final) { + // mark finalized in destination page + p_hdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fh, + SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr->flags); + SPIFFS_CHECK_RES(res); + } + // mark source deleted + res = spiffs_page_delete(fs, src_pix); + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Deletes a page and removes it from object lookup. +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix) { + s32_t res; + spiffs_page_header hdr; + hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); + // mark deleted entry in source object lookup + spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_DELETED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, + 0, + SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&d_obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_deleted++; + fs->stats_p_allocated--; + + // mark deleted in source page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, + 0, + SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&hdr.flags); + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Create an object index header page with empty index and undefined length +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + spiffs_page_object_ix_header oix_hdr; + int entry; + + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("create: found free page @ "_SPIPRIpg" bix:"_SPIPRIbl" entry:"_SPIPRIsp"\n", (spiffs_page_ix)SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write empty object index page + oix_hdr.p_hdr.obj_id = obj_id; + oix_hdr.p_hdr.span_ix = 0; + oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED); + oix_hdr.type = type; + oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page + strncpy((char*)oix_hdr.name, (const char*)name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(oix_hdr.meta, meta, SPIFFS_OBJ_META_LEN); + } else { + memset(oix_hdr.meta, 0xff, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + + // update page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr); + + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&oix_hdr, + SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); + + if (objix_hdr_pix) { + *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// update object index header with any combination of name/size/index +// new_objix_hdr_data may be null, if so the object index header page is loaded +// name may be null, if so name is not changed +// size may be null, if so size is not changed +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix) { + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header *objix_hdr; + spiffs_page_ix new_objix_hdr_pix; + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + if (new_objix_hdr_data) { + // object index header page already given to us, no need to load it + objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data; + } else { + // read object index header page + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + objix_hdr = (spiffs_page_object_ix_header *)fs->work; + } + + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, obj_id, 0); + + // change name + if (name) { + strncpy((char*)objix_hdr->name, (const char*)name, SPIFFS_OBJ_NAME_LEN); + } +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(objix_hdr->meta, meta, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + if (size) { + objix_hdr->size = size; + } + + // move and update page + res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, obj_id, 0, objix_hdr_pix, &new_objix_hdr_pix); + + if (res == SPIFFS_OK) { + if (new_pix) { + *new_pix = new_objix_hdr_pix; + } + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + new_objix_hdr_data ? SPIFFS_EV_IX_UPD : SPIFFS_EV_IX_UPD_HDR, + obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); + if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id_raw, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size) { +#if SPIFFS_IX_MAP == 0 + (void)objix; +#endif + // update index caches in all file descriptors + spiffs_obj_id obj_id = obj_id_raw & ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; +#if SPIFFS_TEMPORAL_FD_CACHE + if (cur_fd->score == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#else + if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#endif + if (spix == 0) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" objix_hdr_pix to "_SPIPRIpg", size:"_SPIPRIi"\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size); + cur_fd->objix_hdr_pix = new_pix; + if (new_size != 0) { + cur_fd->size = new_size; + } + } else { + cur_fd->file_nbr = 0; + cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED; + } + } + if (cur_fd->cursor_objix_spix == spix) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix); + cur_fd->cursor_objix_pix = new_pix; + } else { + cur_fd->cursor_objix_pix = 0; + } + } + } + +#if SPIFFS_IX_MAP + + // update index maps + if (ev == SPIFFS_EV_IX_UPD || ev == SPIFFS_EV_IX_NEW) { + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + // check fd opened, having ix map, match obj id + if (cur_fd->file_nbr == 0 || + cur_fd->ix_map == 0 || + (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; + SPIFFS_DBG(" callback: map ix update fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp"\n", cur_fd->file_nbr, cur_fd->obj_id, spix); + spiffs_update_ix_map(fs, cur_fd, spix, objix); + } + } + +#endif + + // callback to user if object index header + if (fs->file_cb_f && spix == 0 && (obj_id_raw & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_fileop_type op; + if (ev == SPIFFS_EV_IX_NEW) { + op = SPIFFS_CB_CREATED; + } else if (ev == SPIFFS_EV_IX_UPD || + ev == SPIFFS_EV_IX_MOV || + ev == SPIFFS_EV_IX_UPD_HDR) { + op = SPIFFS_CB_UPDATED; + } else if (ev == SPIFFS_EV_IX_DEL) { + op = SPIFFS_CB_DELETED; + } else { + SPIFFS_DBG(" callback: WARNING unknown callback event "_SPIPRIi"\n", ev); + return; // bail out + } + fs->file_cb_f(fs, op, obj_id, new_pix); + } +} + +// Open object by id +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + s32_t res = SPIFFS_OK; + spiffs_page_ix pix; + + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + + return res; +} + +// Open object by page index +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + (void)mode; + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header oix_hdr; + spiffs_obj_id obj_id; + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr); + SPIFFS_CHECK_RES(res); + + spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(fs, pix); + int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix); + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t *)&obj_id); + + fd->fs = fs; + fd->objix_hdr_pix = pix; + fd->size = oix_hdr.size; + fd->offset = 0; + fd->cursor_objix_pix = pix; + fd->cursor_objix_spix = 0; + fd->obj_id = obj_id; + fd->flags = flags; + + SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0); + + SPIFFS_DBG("open: fd "_SPIPRIfd" is obj id "_SPIPRIid"\n", fd->file_nbr, fd->obj_id); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Append to object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + SPIFFS_DBG("append: "_SPIPRIi" bytes @ offs "_SPIPRIi" of size "_SPIPRIi"\n", len, offset, fd->size); + + if (offset > fd->size) { + SPIFFS_DBG("append: offset reversed to size\n"); + offset = fd->size; + } + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); // add an extra page of data worth for meta + if (res != SPIFFS_OK) { + SPIFFS_DBG("append: gc check fail "_SPIPRIi"\n", res); + } + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_page; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_page; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index page, unless first pass + SPIFFS_DBG("append: "_SPIPRIid" store objix "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, prev_objix_spix, written); + if (prev_objix_spix == 0) { + // this is an update to object index header page + objix_hdr->size = offset+written; + if (offset == 0) { + // was an empty object, update same page (size was 0xffffffff) + res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + } else { + // was a nonempty object, update to new page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + new_objix_hdr_page, 0, written); + } + } else { + // this is an update to an object index page + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + // update length in object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new size I "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + offset+written, new_objix_hdr_page, 0, written); + } + fd->size = offset+written; + fd->offset = offset+written; + } + + // create or load new object index page + if (cur_objix_spix == 0) { + // load object index header page, must always exist + SPIFFS_DBG("append: "_SPIPRIid" load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", fd->obj_id, cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs)); + // on subsequent passes, create a new object index page + if (written > 0 || cur_objix_spix > len_objix_spix) { + p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = cur_objix_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + res = spiffs_page_allocate_data(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 1, &cur_objix_pix); + SPIFFS_CHECK_RES(res); + // quick "load" of new object index page + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header)); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); + SPIFFS_DBG("append: "_SPIPRIid" create objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + } else { + // on first pass, we load existing object index page + spiffs_page_ix pix; + SPIFFS_DBG("append: "_SPIPRIid" find objix span_ix:"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("append: "_SPIPRIid" found object index at page "_SPIPRIpg" [fd size "_SPIPRIi"]\n", fd->obj_id, pix, fd->size); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + fd->size = offset+written; + } + prev_objix_spix = cur_objix_spix; + } + + // write data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + if (page_offs == 0) { + // at beginning of a page, allocate and write a new page of data + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_page); + SPIFFS_DBG("append: "_SPIPRIid" store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id, + data_page, data_spix, page_offs, to_write, written); + } else { + // append to existing page, fill out free data in existing page + if (cur_objix_spix == 0) { + // get data page from object index header page + data_page = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_page = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + res = spiffs_page_data_check(fs, fd, data_page, data_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + SPIFFS_DBG("append: "_SPIPRIid" store to existing data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id + , data_page, data_spix, page_offs, to_write, written); + } + + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, data_spix); + objix_hdr->size = offset+written; + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->size = offset+written; + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page, unless object header index page + SPIFFS_DBG("append: "_SPIPRIid" store objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + + // update size in object header index page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store new size II "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi", res "_SPIPRIi"\n", fd->obj_id + , offset+written, new_objix_hdr_page, 0, written, res2); + SPIFFS_CHECK_RES(res2); + } else { + // wrote within object index header page + if (offset == 0) { + // wrote to empty object - simply update size and write whole page + objix_hdr->size = offset+written; + SPIFFS_DBG("append: "_SPIPRIid" store fresh objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD_HDR, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); + } else { + // modifying object index header page, update size and make new copy + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , new_objix_hdr_page, 0, written); + SPIFFS_CHECK_RES(res2); + } + } + + return res; +} // spiffs_object_append +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Modify object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_pix; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_pix; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index (header) page, unless first pass + if (prev_objix_spix == 0) { + // store previous object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res); + } else { + // store new version of previous object index page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store previous modified objix page, "_SPIPRIid":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, objix->p_hdr.span_ix, written); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + + // load next object index page + if (cur_objix_spix == 0) { + // load object index header page, must exist + SPIFFS_DBG("modify: load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + // load existing object index page on first pass + spiffs_page_ix pix; + SPIFFS_DBG("modify: find objix span_ix:"_SPIPRIsp"\n", cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("modify: found object index at page "_SPIPRIpg"\n", pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + prev_objix_spix = cur_objix_spix; + } + + // write partial data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + spiffs_page_ix orig_data_pix; + if (cur_objix_spix == 0) { + // get data page from object index header page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) { + // a full page, allocate and write a new page of data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_pix); + SPIFFS_DBG("modify: store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", data_pix, data_spix, page_offs, to_write, written); + } else { + // write to existing page, allocate new and copy unmodified data + + res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &data_pix); + if (res != SPIFFS_OK) break; + + // copy unmodified data + if (page_offs > 0) { + // before modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header), + page_offs); + if (res != SPIFFS_OK) break; + } + if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) { + // after modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write)); + if (res != SPIFFS_OK) break; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + SPIFFS_DBG("modify: store to existing data page, src:"_SPIPRIpg", dst:"_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); + } + + // delete original data page + res = spiffs_page_delete(fs, orig_data_pix); + if (res != SPIFFS_OK) break; + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", data_pix, data_spix); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page + // move and update page + spiffs_page_ix new_objix_pix; + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store modified objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, cur_objix_spix, written); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + + } else { + // wrote within object index header page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res2); + } + + return res; +} // spiffs_object_modify +#endif // !SPIFFS_READ_ONLY + +static s32_t spiffs_object_find_object_index_header_by_name_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_var_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_OK; + } + } + + return SPIFFS_VIS_COUNTINUE; +} + +// Finds object index header page by name +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + 0, + 0, + spiffs_object_find_object_index_header_by_name_v, + name, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if !SPIFFS_READ_ONLY +// Truncates object to new size. If new size is null, object may be removed totally +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_size, + u8_t remove_full) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + + if ((fd->size == SPIFFS_UNDEFINED_LEN || fd->size == 0) && !remove_full) { + // no op + return res; + } + + // need 2 pages if not removing: object index page + possibly chopped data page + if (remove_full == 0) { + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2); + SPIFFS_CHECK_RES(res); + } + + spiffs_page_ix objix_pix = fd->objix_hdr_pix; + spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_size = fd->size == (u32_t)SPIFFS_UNDEFINED_LEN ? 0 : fd->size ; + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_ix data_pix; + spiffs_page_ix new_objix_hdr_pix; + + // before truncating, check if object is to be fully removed and mark this + if (remove_full && new_size == 0) { + u8_t flags = ~( SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + SPIFFS_CHECK_RES(res); + } + + // delete from end of object until desired len is reached + while (cur_size > new_size) { + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // put object index for current data span index in work buffer + if (prev_objix_spix != cur_objix_spix) { + if (prev_objix_spix != (spiffs_span_ix)-1) { + // remove previous object index page + SPIFFS_DBG("truncate: delete objix page "_SPIPRIpg":"_SPIPRIsp"\n", objix_pix, prev_objix_spix); + + res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0); + if (prev_objix_spix > 0) { + // Update object index header page, unless we totally want to remove the file. + // If fully removing, we're not keeping consistency as good as when storing the header between chunks, + // would we be aborted. But when removing full files, a crammed system may otherwise + // report ERR_FULL a la windows. We cannot have that. + // Hence, take the risk - if aborted, a file check would free the lost pages and mend things + // as the file is marked as fully deleted in the beginning. + if (remove_full == 0) { + SPIFFS_DBG("truncate: update objix hdr page "_SPIPRIpg":"_SPIPRIsp" to size "_SPIPRIi"\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + } + } + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + + SPIFFS_DBG("truncate: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE; + } + + SPIFFS_DBG("truncate: got data pix "_SPIPRIpg"\n", data_pix); + + if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) { + // delete full data page + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_ERR_DELETED && res != SPIFFS_OK && res != SPIFFS_ERR_INDEX_REF_FREE) { + SPIFFS_DBG("truncate: err validating data pix "_SPIPRIi"\n", res); + break; + } + + if (res == SPIFFS_OK) { + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) { + SPIFFS_DBG("truncate: err deleting data pix "_SPIPRIi"\n", res); + break; + } + } else if (res == SPIFFS_ERR_DELETED || res == SPIFFS_ERR_INDEX_REF_FREE) { + res = SPIFFS_OK; + } + + // update current size + if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) { + cur_size -= SPIFFS_DATA_PAGE_SIZE(fs); + } else { + cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs); + } + fd->size = cur_size; + fd->offset = cur_size; + SPIFFS_DBG("truncate: delete data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", data_pix, data_spix, cur_size); + } else { + // delete last page, partially + spiffs_page_header p_hdr; + spiffs_page_ix new_data_pix; + u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_DBG("truncate: delete "_SPIPRIi" bytes from data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", bytes_to_remove, data_pix, data_spix, cur_size); + + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_OK) break; + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + // allocate new page and copy unmodified data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &new_data_pix); + if (res != SPIFFS_OK) break; + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove); + if (res != SPIFFS_OK) break; + // delete original data page + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + cur_size = new_size; + fd->size = new_size; + fd->offset = cur_size; + break; + } + data_spix--; + } // while all data + + // update object indices + if (cur_objix_spix == 0) { + // update object index header page + if (cur_size == 0) { + if (remove_full) { + // remove object altogether + SPIFFS_DBG("truncate: remove object index header page "_SPIPRIpg"\n", objix_pix); + + res = spiffs_page_index_check(fs, fd, objix_pix, 0); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); + } else { + // make uninitialized object + SPIFFS_DBG("truncate: reset objix_hdr page "_SPIPRIpg"\n", objix_pix); + memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff, + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header)); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update object index header page + SPIFFS_DBG("truncate: update object index header page with indices and size\n"); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update both current object index page and object index header page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res); + + // move and update object index page + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, fd->obj_id, 0, objix_pix, &new_objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + SPIFFS_DBG("truncate: store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, cur_objix_spix); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + // update object index header page with new size + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + + return res; +} // spiffs_object_truncate +#endif // !SPIFFS_READ_ONLY + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + spiffs_page_ix objix_pix; + spiffs_page_ix data_pix; + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_offset = offset; + spiffs_span_ix cur_objix_spix; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + while (cur_offset < offset + len) { +#if SPIFFS_IX_MAP + // check if we have a memory, index map and if so, if we're within index map's range + // and if so, if the entry is populated + if (fd->ix_map && data_spix >= fd->ix_map->start_spix && data_spix <= fd->ix_map->end_spix + && fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]) { + data_pix = fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]; + } else { +#endif + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (prev_objix_spix != cur_objix_spix) { + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + SPIFFS_DBG("read: find objix "_SPIPRIid":"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + objix_pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + } + SPIFFS_DBG("read: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); + + fd->offset = cur_offset; + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } +#if SPIFFS_IX_MAP + } +#endif + // all remaining data + u32_t len_to_read = offset + len - cur_offset; + // remaining data in page + len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + // remaining data in file + len_to_read = MIN(len_to_read, fd->size); + SPIFFS_DBG("read: offset:"_SPIPRIi" rd:"_SPIPRIi" data spix:"_SPIPRIsp" is data_pix:"_SPIPRIpg" addr:"_SPIPRIad"\n", cur_offset, len_to_read, data_spix, data_pix, + (u32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)))); + if (len_to_read <= 0) { + res = SPIFFS_ERR_END_OF_OBJECT; + break; + } + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + SPIFFS_CHECK_RES(res); + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)), + len_to_read, + dst); + SPIFFS_CHECK_RES(res); + dst += len_to_read; + cur_offset += len_to_read; + fd->offset = cur_offset; + data_spix++; + } + + return res; +} + +#if !SPIFFS_READ_ONLY +typedef struct { + spiffs_obj_id min_obj_id; + spiffs_obj_id max_obj_id; + u32_t compaction; + const u8_t *conflicting_name; +} spiffs_free_obj_id_state; + +static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p) { + if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) { + spiffs_obj_id min_obj_id = *((spiffs_obj_id*)user_var_p); + const u8_t *conflicting_name = (const u8_t*)user_const_p; + + // if conflicting name parameter is given, also check if this name is found in object index hdrs + if (conflicting_name && (id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + int res; + spiffs_page_object_ix_header objix_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + } + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t bit_ix = (id-min_obj_id) & 7; + int byte_ix = (id-min_obj_id) >> 3; + if (byte_ix >= 0 && (u32_t)byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + fs->work[byte_ix] |= (1<conflicting_name && strcmp((const char *)state->conflicting_name, (char *)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + if (id >= state->min_obj_id && id <= state->max_obj_id) { + u8_t *map = (u8_t *)fs->work; + int ix = (id - state->min_obj_id) / state->compaction; + //SPIFFS_DBG("free_obj_id: add ix "_SPIPRIi" for id "_SPIPRIid" min"_SPIPRIid" max"_SPIPRIid" comp:"_SPIPRIi"\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); + map[ix]++; + } + } + } + return SPIFFS_VIS_COUNTINUE; +} + +// Scans thru all object lookup for object index header pages. If total possible number of +// object ids cannot fit into a work buffer, these are grouped. When a group containing free +// object ids is found, the object lu is again scanned for object ids within group and bitmasked. +// Finally, the bitmask is searched for a free id +s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, const u8_t *conflicting_name) { + s32_t res = SPIFFS_OK; + u32_t max_objects = (fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2; + spiffs_free_obj_id_state state; + spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE; + state.min_obj_id = 1; + state.max_obj_id = max_objects + 1; + if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) { + state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG; + } + state.compaction = 0; + state.conflicting_name = conflicting_name; + while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) { + if (state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) { + // possible to represent in bitmap + u32_t i, j; + SPIFFS_DBG("free_obj_id: BITM min:"_SPIPRIid" max:"_SPIPRIid"\n", state.min_obj_id, state.max_obj_id); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, + conflicting_name, &state.min_obj_id, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + // traverse bitmask until found free obj_id + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) { + u8_t mask = fs->work[i]; + if (mask == 0xff) { + continue; + } + for (j = 0; j < 8; j++) { + if ((mask & (1<work; + u8_t min_count = 0xff; + + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) { + if (map[i] < min_count) { + min_count = map[i]; + min_i = i; + if (min_count == 0) { + break; + } + } + } + + if (min_count == state.compaction) { + // there are no free objids! + SPIFFS_DBG("free_obj_id: compacted table is full\n"); + return SPIFFS_ERR_FULL; + } + + SPIFFS_DBG("free_obj_id: COMP select index:"_SPIPRIi" min_count:"_SPIPRIi" min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); + + if (min_count == 0) { + // no id in this range, skip compacting and use directly + *obj_id = min_i * state.compaction + state.min_obj_id; + return SPIFFS_OK; + } else { + SPIFFS_DBG("free_obj_id: COMP SEL chunk:"_SPIPRIi" min:"_SPIPRIid" -> "_SPIPRIid"\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); + state.min_obj_id += min_i * state.compaction; + state.max_obj_id = state.min_obj_id + state.compaction; + // decrease compaction + } + if ((state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) { + // no need for compacting, use bitmap + continue; + } + } + // in a work memory of log_page_size bytes, we may fit in log_page_size ids + // todo what if compaction is > 255 - then we cannot fit it in a byte + state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t))); + SPIFFS_DBG("free_obj_id: COMP min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", state.min_obj_id, state.max_obj_id, state.compaction); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, &state, 0, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + state.conflicting_name = 0; // searched for conflicting name once, no need to do it again + } + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_TEMPORAL_FD_CACHE +// djb2 hash +static u32_t spiffs_hash(spiffs *fs, const u8_t *name) { + (void)fs; + u32_t hash = 5381; + u8_t c; + int i = 0; + while ((c = name[i++]) && i < SPIFFS_OBJ_NAME_LEN) { + hash = (hash * 33) ^ c; + } + return hash; +} +#endif + +s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd, const char *name) { +#if SPIFFS_TEMPORAL_FD_CACHE + u32_t i; + u16_t min_score = 0xffff; + u32_t cand_ix = (u32_t)-1; + u32_t name_hash = name ? spiffs_hash(fs, (const u8_t *)name) : 0; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + + if (name) { + // first, decrease score of all closed descriptors + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (cur_fd->score > 1) { // score == 0 indicates never used fd + cur_fd->score--; + } + } + } + } + + // find the free fd with least score + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (name && cur_fd->name_hash == name_hash) { + cand_ix = i; + break; + } + if (cur_fd->score < min_score) { + min_score = cur_fd->score; + cand_ix = i; + } + } + } + + if (cand_ix != (u32_t)-1) { + spiffs_fd *cur_fd = &fds[cand_ix]; + if (name) { + if (cur_fd->name_hash == name_hash && cur_fd->score > 0) { + // opened an fd with same name hash, assume same file + // set search point to saved obj index page and hope we have a correct match directly + // when start searching - if not, we will just keep searching until it is found + fs->cursor_block_ix = SPIFFS_BLOCK_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + fs->cursor_obj_lu_entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + // update score + if (cur_fd->score < 0xffff-SPIFFS_TEMPORAL_CACHE_HIT_SCORE) { + cur_fd->score += SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + } else { + cur_fd->score = 0xffff; + } + } else { + // no hash hit, restore this fd to initial state + cur_fd->score = SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + cur_fd->name_hash = name_hash; + } + } + cur_fd->file_nbr = cand_ix+1; + *fd = cur_fd; + return SPIFFS_OK; + } else { + return SPIFFS_ERR_OUT_OF_FILE_DESCS; + } +#else + (void)name; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + cur_fd->file_nbr = i+1; + *fd = cur_fd; + return SPIFFS_OK; + } + } + return SPIFFS_ERR_OUT_OF_FILE_DESCS; +#endif +} + +s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + spiffs_fd *fd = &fds[f-1]; + if (fd->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + fd->file_nbr = 0; +#if SPIFFS_IX_MAP + fd->ix_map = 0; +#endif + return SPIFFS_OK; +} + +s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + *fd = &fds[f-1]; + if ((*fd)->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + return SPIFFS_OK; +} + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path) { + u32_t i; + u32_t old_hash = spiffs_hash(fs, (const u8_t *)old_path); + u32_t new_hash = spiffs_hash(fs, (const u8_t *)new_path); + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->score > 0 && cur_fd->name_hash == old_hash) { + cur_fd->name_hash = new_hash; + } + } +} +#endif diff --git a/components/mkspiffs/src/spiffs/spiffs_nucleus.h b/components/mkspiffs/src/spiffs/spiffs_nucleus.h new file mode 100644 index 0000000..7d676ee --- /dev/null +++ b/components/mkspiffs/src/spiffs/spiffs_nucleus.h @@ -0,0 +1,797 @@ +/* + * spiffs_nucleus.h + * + * Created on: Jun 15, 2013 + * Author: petera + */ + +/* SPIFFS layout + * + * spiffs is designed for following spi flash characteristics: + * - only big areas of data (blocks) can be erased + * - erasing resets all bits in a block to ones + * - writing pulls ones to zeroes + * - zeroes cannot be pulled to ones, without erase + * - wear leveling + * + * spiffs is also meant to be run on embedded, memory constraint devices. + * + * Entire area is divided in blocks. Entire area is also divided in pages. + * Each block contains same number of pages. A page cannot be erased, but a + * block can be erased. + * + * Entire area must be block_size * x + * page_size must be block_size / (2^y) where y > 2 + * + * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes + * + * BLOCK 0 PAGE 0 object lookup 1 + * PAGE 1 object lookup 2 + * ... + * PAGE n-1 object lookup n + * PAGE n object data 1 + * PAGE n+1 object data 2 + * ... + * PAGE n+m-1 object data m + * + * BLOCK 1 PAGE n+m object lookup 1 + * PAGE n+m+1 object lookup 2 + * ... + * PAGE 2n+m-1 object lookup n + * PAGE 2n+m object data 1 + * PAGE 2n+m object data 2 + * ... + * PAGE 2n+2m-1 object data m + * ... + * + * n is number of object lookup pages, which is number of pages needed to index all pages + * in a block by object id + * : block_size / page_size * sizeof(obj_id) / page_size + * m is number data pages, which is number of pages in block minus number of lookup pages + * : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size + * thus, n+m is total number of pages in a block + * : block_size / page_size + * + * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256 + * + * Object lookup pages contain object id entries. Each entry represent the corresponding + * data page. + * Assuming a 16 bit object id, an object id being 0xffff represents a free page. + * An object id being 0x0000 represents a deleted page. + * + * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff .. + * page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff .. + * page 2 : data : data for object id 0008 + * page 3 : data : data for object id 0001 + * page 4 : data : data for object id 0aaa + * ... + * + * + * Object data pages can be either object index pages or object content. + * All object data pages contains a data page header, containing object id and span index. + * The span index denotes the object page ordering amongst data pages with same object id. + * This applies to both object index pages (when index spans more than one page of entries), + * and object data pages. + * An object index page contains page entries pointing to object content page. The entry index + * in a object index page correlates to the span index in the actual object data page. + * The first object index page (span index 0) is called object index header page, and also + * contains object flags (directory/file), size, object name etc. + * + * ex: + * BLOCK 1 + * PAGE 256: objectl lookup page 1 + * [*123] [ 123] [ 123] [ 123] + * [ 123] [*123] [ 123] [ 123] + * [free] [free] [free] [free] ... + * PAGE 257: objectl lookup page 2 + * [free] [free] [free] [free] ... + * PAGE 258: object index page (header) + * obj.id:0123 span.ix:0000 flags:INDEX + * size:1600 name:ex.txt type:file + * [259] [260] [261] [262] + * PAGE 259: object data page + * obj.id:0123 span.ix:0000 flags:DATA + * PAGE 260: object data page + * obj.id:0123 span.ix:0001 flags:DATA + * PAGE 261: object data page + * obj.id:0123 span.ix:0002 flags:DATA + * PAGE 262: object data page + * obj.id:0123 span.ix:0003 flags:DATA + * PAGE 263: object index page + * obj.id:0123 span.ix:0001 flags:INDEX + * [264] [265] [fre] [fre] + * [fre] [fre] [fre] [fre] + * PAGE 264: object data page + * obj.id:0123 span.ix:0004 flags:DATA + * PAGE 265: object data page + * obj.id:0123 span.ix:0005 flags:DATA + * + */ +#ifndef SPIFFS_NUCLEUS_H_ +#define SPIFFS_NUCLEUS_H_ + +#define _SPIFFS_ERR_CHECK_FIRST (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_OBJ_ID_MISM (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_SPIX_MISM (SPIFFS_ERR_INTERNAL - 2) +#define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3) +#define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4) + +// visitor result, continue searching +#define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20) +// visitor result, continue searching after reloading lu buffer +#define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21) +// visitor result, stop searching +#define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22) + +// updating an object index contents +#define SPIFFS_EV_IX_UPD (0) +// creating a new object index +#define SPIFFS_EV_IX_NEW (1) +// deleting an object index +#define SPIFFS_EV_IX_DEL (2) +// moving an object index without updating contents +#define SPIFFS_EV_IX_MOV (3) +// updating an object index header data only, not the table itself +#define SPIFFS_EV_IX_UPD_HDR (4) + +#define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1))) + +#define SPIFFS_UNDEFINED_LEN (u32_t)(-1) + +#define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0) +#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) + +#if SPIFFS_USE_MAGIC +#if !SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs))) +#else // SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix)))) +#endif // SPIFFS_USE_MAGIC_LENGTH +#endif // SPIFFS_USE_MAGIC + +#define SPIFFS_CONFIG_MAGIC (0x20090315) + +#if SPIFFS_SINGLETON == 0 +#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \ + ((fs)->cfg.log_page_size) +#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \ + ((fs)->cfg.log_block_size) +#define SPIFFS_CFG_PHYS_SZ(fs) \ + ((fs)->cfg.phys_size) +#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \ + ((fs)->cfg.phys_erase_block) +#define SPIFFS_CFG_PHYS_ADDR(fs) \ + ((fs)->cfg.phys_addr) +#endif + +// total number of pages +#define SPIFFS_MAX_PAGES(fs) \ + ( SPIFFS_CFG_PHYS_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// total number of pages per block, including object lookup pages +#define SPIFFS_PAGES_PER_BLOCK(fs) \ + ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// number of object lookup pages per block +#define SPIFFS_OBJ_LOOKUP_PAGES(fs) \ + (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) ) +// checks if page index belongs to object lookup +#define SPIFFS_IS_LOOKUP_PAGE(fs,pix) \ + (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// number of object lookup entries in all object lookup pages +#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \ + (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// converts a block to physical address +#define SPIFFS_BLOCK_TO_PADDR(fs, block) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) ) +// converts a object lookup entry to page index +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \ + ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry)) +// converts a object lookup entry to physical address of corresponding page +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \ + (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a page to physical address +#define SPIFFS_PAGE_TO_PADDR(fs, page) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a physical address to page +#define SPIFFS_PADDR_TO_PAGE(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// gives index in page for a physical address +#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// returns containing block for given page +#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \ + ( (page) / SPIFFS_PAGES_PER_BLOCK(fs) ) +// returns starting page for block +#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \ + ( (block) * SPIFFS_PAGES_PER_BLOCK(fs) ) +// converts page to entry in object lookup page +#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \ + ( (page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) ) +// returns data size in a data page +#define SPIFFS_DATA_PAGE_SIZE(fs) \ + ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) ) +// returns physical address for block's erase count, +// always in the physical last entry of the last object lookup page +#define SPIFFS_ERASE_COUNT_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id) ) +// returns physical address for block's magic, +// always in the physical second last entry of the last object lookup page +#define SPIFFS_MAGIC_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id)*2 ) +// checks if there is any room for magic in the object luts +#define SPIFFS_CHECK_MAGIC_POSSIBLE(fs) \ + ( (SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) % (SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(spiffs_obj_id))) * sizeof(spiffs_obj_id) \ + <= (SPIFFS_CFG_LOG_PAGE_SZ(fs)-sizeof(spiffs_obj_id)*2) ) + +// define helpers object + +// entries in an object header page index +#define SPIFFS_OBJ_HDR_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix)) +// entries in an object page index +#define SPIFFS_OBJ_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix)) +// object index entry for given data span index +#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs))) +// object index span index number for given data span index or entry +#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) +// get data span index for object index span index +#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \ + ( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) ) + +#define SPIFFS_OP_T_OBJ_LU (0<<0) +#define SPIFFS_OP_T_OBJ_LU2 (1<<0) +#define SPIFFS_OP_T_OBJ_IX (2<<0) +#define SPIFFS_OP_T_OBJ_DA (3<<0) +#define SPIFFS_OP_C_DELE (0<<2) +#define SPIFFS_OP_C_UPDT (1<<2) +#define SPIFFS_OP_C_MOVS (2<<2) +#define SPIFFS_OP_C_MOVD (3<<2) +#define SPIFFS_OP_C_FLSH (4<<2) +#define SPIFFS_OP_C_READ (5<<2) +#define SPIFFS_OP_C_WRTHRU (6<<2) + +#define SPIFFS_OP_TYPE_MASK (3<<0) +#define SPIFFS_OP_COM_MASK (7<<2) + + +// if 0, this page is written to, else clean +#define SPIFFS_PH_FLAG_USED (1<<0) +// if 0, writing is finalized, else under modification +#define SPIFFS_PH_FLAG_FINAL (1<<1) +// if 0, this is an index page, else a data page +#define SPIFFS_PH_FLAG_INDEX (1<<2) +// if 0, page is deleted, else valid +#define SPIFFS_PH_FLAG_DELET (1<<7) +// if 0, this index header is being deleted +#define SPIFFS_PH_FLAG_IXDELE (1<<6) + + +#define SPIFFS_CHECK_MOUNT(fs) \ + ((fs)->mounted != 0) + +#define SPIFFS_CHECK_CFG(fs) \ + ((fs)->config_magic == SPIFFS_CONFIG_MAGIC) + +#define SPIFFS_CHECK_RES(res) \ + do { \ + if ((res) < SPIFFS_OK) return (res); \ + } while (0); + +#define SPIFFS_API_CHECK_MOUNT(fs) \ + if (!SPIFFS_CHECK_MOUNT((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \ + return SPIFFS_ERR_NOT_MOUNTED; \ + } + +#define SPIFFS_API_CHECK_CFG(fs) \ + if (!SPIFFS_CHECK_CFG((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \ + return SPIFFS_ERR_NOT_CONFIGURED; \ + } + +#define SPIFFS_API_CHECK_RES(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + return (res); \ + } + +#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + SPIFFS_UNLOCK(fs); \ + return (res); \ + } + +#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \ + if (((objid) & SPIFFS_OBJ_ID_IX_FLAG) == 0) return SPIFFS_ERR_NOT_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH; + //if ((spix) == 0 && ((ph).flags & SPIFFS_PH_FLAG_IXDELE) == 0) return SPIFFS_ERR_DELETED; + +#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \ + if ((objid) & SPIFFS_OBJ_ID_IX_FLAG) return SPIFFS_ERR_IS_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; + + +// check id, only visit matching objec ids +#define SPIFFS_VIS_CHECK_ID (1<<0) +// report argument object id to visitor - else object lookup id is reported +#define SPIFFS_VIS_CHECK_PH (1<<1) +// stop searching at end of all look up pages +#define SPIFFS_VIS_NO_WRAP (1<<2) + +#if SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len)) + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_paddr), (_len)) + +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +#if SPIFFS_CACHE + +#define SPIFFS_CACHE_FLAG_DIRTY (1<<0) +#define SPIFFS_CACHE_FLAG_WRTHRU (1<<1) +#define SPIFFS_CACHE_FLAG_OBJLU (1<<2) +#define SPIFFS_CACHE_FLAG_OBJIX (1<<3) +#define SPIFFS_CACHE_FLAG_DATA (1<<4) +#define SPIFFS_CACHE_FLAG_TYPE_WR (1<<7) + +#define SPIFFS_CACHE_PAGE_SIZE(fs) \ + (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)) + +#define spiffs_get_cache(fs) \ + ((spiffs_cache *)((fs)->cache)) + +#define spiffs_get_cache_page_hdr(fs, c, ix) \ + ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)]))) + +#define spiffs_get_cache_page(fs, c, ix) \ + ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page)) + +// cache page struct +typedef struct { + // cache flags + u8_t flags; + // cache page index + u8_t ix; + // last access of this cache page + u32_t last_access; + union { + // type read cache + struct { + // read cache page index + spiffs_page_ix pix; + }; +#if SPIFFS_CACHE_WR + // type write cache + struct { + // write cache + spiffs_obj_id obj_id; + // offset in cache page + u32_t offset; + // size of cache page + u16_t size; + }; +#endif + }; +} spiffs_cache_page; + +// cache struct +typedef struct { + u8_t cpage_count; + u32_t last_access; + u32_t cpage_use_map; + u32_t cpage_use_mask; + u8_t *cpages; +} spiffs_cache; + +#endif + + +// spiffs nucleus file descriptor +typedef struct { + // the filesystem of this descriptor + spiffs *fs; + // number of file descriptor - if 0, the file descriptor is closed + spiffs_file file_nbr; + // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted + spiffs_obj_id obj_id; + // size of the file + u32_t size; + // cached object index header page index + spiffs_page_ix objix_hdr_pix; + // cached offset object index page index + spiffs_page_ix cursor_objix_pix; + // cached offset object index span index + spiffs_span_ix cursor_objix_spix; + // current absolute offset + u32_t offset; + // current file descriptor offset + u32_t fdoffset; + // fd flags + spiffs_flags flags; +#if SPIFFS_CACHE_WR + spiffs_cache_page *cache_page; +#endif +#if SPIFFS_TEMPORAL_FD_CACHE + // djb2 hash of filename + u32_t name_hash; + // hit score (score == 0 indicates never used fd) + u16_t score; +#endif +#if SPIFFS_IX_MAP + // spiffs index map, if 0 it means unmapped + spiffs_ix_map *ix_map; +#endif +} spiffs_fd; + + +// object structs + +// page header, part of each page except object lookup pages +// NB: this is always aligned when the data page is an object index, +// as in this case struct spiffs_page_object_ix is used +typedef struct __attribute(( packed )) { + // object id + spiffs_obj_id obj_id; + // object span index + spiffs_span_ix span_ix; + // flags + u8_t flags; +} spiffs_page_header; + +// object index header page header +typedef struct __attribute(( packed )) +#if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES + __attribute(( aligned(sizeof(spiffs_page_ix)) )) +#endif +{ + // common page header + spiffs_page_header p_hdr; + // alignment + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; + // size of object + u32_t size; + // type of object + spiffs_obj_type type; + // name of object + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + // metadata. not interpreted by SPIFFS in any way. + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_page_object_ix_header; + +// object index page header +typedef struct __attribute(( packed )) { + spiffs_page_header p_hdr; + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; +} spiffs_page_object_ix; + +// callback func for object lookup visitor +typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p); + + +#if SPIFFS_CACHE +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src)) +#else +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (addr), (len), (src)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +// --------------- + +s32_t spiffs_phys_rd( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *dst); + +s32_t spiffs_phys_wr( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *src); + +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len); + +s32_t spiffs_phys_count_free_blocks( + spiffs *fs); + +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix); + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH +s32_t spiffs_probe( + spiffs_config *cfg); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH + +// --------------- + +s32_t spiffs_obj_lu_scan( + spiffs *fs); + +s32_t spiffs_obj_lu_find_free_obj_id( + spiffs *fs, + spiffs_obj_id *obj_id, + const u8_t *conflicting_name); + +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix); + +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix); + +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix); + +// --------------- + +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix); + +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix); + +#if SPIFFS_IX_MAP + +s32_t spiffs_populate_ix_map( + spiffs *fs, + spiffs_fd *fd, + u32_t vec_entry_start, + u32_t vec_entry_end); + +#endif + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size); + +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_append( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_modify( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst); + +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_len, + u8_t remove_object); + +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len); + +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidate, + int *candidate_count, + char fs_crammed); + +s32_t spiffs_gc_clean( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages); + +// --------------- + +s32_t spiffs_fd_find_new( + spiffs *fs, + spiffs_fd **fd, + const char *name); + +s32_t spiffs_fd_return( + spiffs *fs, + spiffs_file f); + +s32_t spiffs_fd_get( + spiffs *fs, + spiffs_file f, + spiffs_fd **fd); + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path); +#endif + +#if SPIFFS_CACHE +void spiffs_cache_init( + spiffs *fs); + +void spiffs_cache_drop_page( + spiffs *fs, + spiffs_page_ix pix); + +#if SPIFFS_CACHE_WR +spiffs_cache_page *spiffs_cache_page_allocate_by_fd( + spiffs *fs, + spiffs_fd *fd); + +void spiffs_cache_fd_release( + spiffs *fs, + spiffs_cache_page *cp); + +spiffs_cache_page *spiffs_cache_page_get_by_fd( + spiffs *fs, + spiffs_fd *fd); +#endif +#endif + +s32_t spiffs_lookup_consistency_check( + spiffs *fs, + u8_t check_all_objects); + +s32_t spiffs_page_consistency_check( + spiffs *fs); + +s32_t spiffs_object_index_consistency_check( + spiffs *fs); + +#endif /* SPIFFS_NUCLEUS_H_ */ diff --git a/components/mkspiffs/src/tclap/Arg.h b/components/mkspiffs/src/tclap/Arg.h new file mode 100644 index 0000000..d653164 --- /dev/null +++ b/components/mkspiffs/src/tclap/Arg.h @@ -0,0 +1,692 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: Arg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_ARGUMENT_H +#define TCLAP_ARGUMENT_H + +#ifdef HAVE_CONFIG_H +#include +#else +#define HAVE_SSTREAM +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_SSTREAM) +#include +typedef std::istringstream istringstream; +#elif defined(HAVE_STRSTREAM) +#include +typedef std::istrstream istringstream; +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + +#include "ArgException.h" +#include "Visitor.h" +#include "CmdLineInterface.h" +#include "ArgTraits.h" +#include "StandardTraits.h" + +namespace TCLAP { + +/** + * A virtual base class that defines the essential data for all arguments. + * This class, or one of its existing children, must be subclassed to do + * anything. + */ +class Arg +{ + private: + /** + * Prevent accidental copying. + */ + Arg(const Arg& rhs); + + /** + * Prevent accidental copying. + */ + Arg& operator=(const Arg& rhs); + + /** + * Indicates whether the rest of the arguments should be ignored. + */ + static bool& ignoreRestRef() { static bool ign = false; return ign; } + + /** + * The delimiter that separates an argument flag/name from the + * value. + */ + static char& delimiterRef() { static char delim = ' '; return delim; } + + protected: + + /** + * The single char flag used to identify the argument. + * This value (preceded by a dash {-}), can be used to identify + * an argument on the command line. The _flag can be blank, + * in fact this is how unlabeled args work. Unlabeled args must + * override appropriate functions to get correct handling. Note + * that the _flag does NOT include the dash as part of the flag. + */ + std::string _flag; + + /** + * A single work namd indentifying the argument. + * This value (preceded by two dashed {--}) can also be used + * to identify an argument on the command line. Note that the + * _name does NOT include the two dashes as part of the _name. The + * _name cannot be blank. + */ + std::string _name; + + /** + * Description of the argument. + */ + std::string _description; + + /** + * Indicating whether the argument is required. + */ + bool _required; + + /** + * Label to be used in usage description. Normally set to + * "required", but can be changed when necessary. + */ + std::string _requireLabel; + + /** + * Indicates whether a value is required for the argument. + * Note that the value may be required but the argument/value + * combination may not be, as specified by _required. + */ + bool _valueRequired; + + /** + * Indicates whether the argument has been set. + * Indicates that a value on the command line has matched the + * name/flag of this argument and the values have been set accordingly. + */ + bool _alreadySet; + + /** + * A pointer to a vistitor object. + * The visitor allows special handling to occur as soon as the + * argument is matched. This defaults to NULL and should not + * be used unless absolutely necessary. + */ + Visitor* _visitor; + + /** + * Whether this argument can be ignored, if desired. + */ + bool _ignoreable; + + /** + * Indicates that the arg was set as part of an XOR and not on the + * command line. + */ + bool _xorSet; + + bool _acceptsMultipleValues; + + /** + * Performs the special handling described by the Vistitor. + */ + void _checkWithVisitor() const; + + /** + * Primary constructor. YOU (yes you) should NEVER construct an Arg + * directly, this is a base class that is extended by various children + * that are meant to be used. Use SwitchArg, ValueArg, MultiArg, + * UnlabeledValueArg, or UnlabeledMultiArg instead. + * + * \param flag - The flag identifying the argument. + * \param name - The name identifying the argument. + * \param desc - The description of the argument, used in the usage. + * \param req - Whether the argument is required. + * \param valreq - Whether the a value is required for the argument. + * \param v - The visitor checked by the argument. Defaults to NULL. + */ + Arg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + bool valreq, + Visitor* v = NULL ); + + public: + /** + * Destructor. + */ + virtual ~Arg(); + + /** + * Adds this to the specified list of Args. + * \param argList - The list to add this to. + */ + virtual void addToList( std::list& argList ) const; + + /** + * Begin ignoring arguments since the "--" argument was specified. + */ + static void beginIgnoring() { ignoreRestRef() = true; } + + /** + * Whether to ignore the rest. + */ + static bool ignoreRest() { return ignoreRestRef(); } + + /** + * The delimiter that separates an argument flag/name from the + * value. + */ + static char delimiter() { return delimiterRef(); } + + /** + * The char used as a place holder when SwitchArgs are combined. + * Currently set to the bell char (ASCII 7). + */ + static char blankChar() { return (char)7; } + + /** + * The char that indicates the beginning of a flag. Defaults to '-', but + * clients can define TCLAP_FLAGSTARTCHAR to override. + */ +#ifndef TCLAP_FLAGSTARTCHAR +#define TCLAP_FLAGSTARTCHAR '-' +#endif + static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; } + + /** + * The sting that indicates the beginning of a flag. Defaults to "-", but + * clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same + * as TCLAP_FLAGSTARTCHAR. + */ +#ifndef TCLAP_FLAGSTARTSTRING +#define TCLAP_FLAGSTARTSTRING "-" +#endif + static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; } + + /** + * The sting that indicates the beginning of a name. Defaults to "--", but + * clients can define TCLAP_NAMESTARTSTRING to override. + */ +#ifndef TCLAP_NAMESTARTSTRING +#define TCLAP_NAMESTARTSTRING "--" +#endif + static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; } + + /** + * The name used to identify the ignore rest argument. + */ + static const std::string ignoreNameString() { return "ignore_rest"; } + + /** + * Sets the delimiter for all arguments. + * \param c - The character that delimits flags/names from values. + */ + static void setDelimiter( char c ) { delimiterRef() = c; } + + /** + * Pure virtual method meant to handle the parsing and value assignment + * of the string on the command line. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. What is + * passed in from main. + */ + virtual bool processArg(int *i, std::vector& args) = 0; + + /** + * Operator ==. + * Equality operator. Must be virtual to handle unlabeled args. + * \param a - The Arg to be compared to this. + */ + virtual bool operator==(const Arg& a) const; + + /** + * Returns the argument flag. + */ + const std::string& getFlag() const; + + /** + * Returns the argument name. + */ + const std::string& getName() const; + + /** + * Returns the argument description. + */ + std::string getDescription() const; + + /** + * Indicates whether the argument is required. + */ + virtual bool isRequired() const; + + /** + * Sets _required to true. This is used by the XorHandler. + * You really have no reason to ever use it. + */ + void forceRequired(); + + /** + * Sets the _alreadySet value to true. This is used by the XorHandler. + * You really have no reason to ever use it. + */ + void xorSet(); + + /** + * Indicates whether a value must be specified for argument. + */ + bool isValueRequired() const; + + /** + * Indicates whether the argument has already been set. Only true + * if the arg has been matched on the command line. + */ + bool isSet() const; + + /** + * Indicates whether the argument can be ignored, if desired. + */ + bool isIgnoreable() const; + + /** + * A method that tests whether a string matches this argument. + * This is generally called by the processArg() method. This + * method could be re-implemented by a child to change how + * arguments are specified on the command line. + * \param s - The string to be compared to the flag/name to determine + * whether the arg matches. + */ + virtual bool argMatches( const std::string& s ) const; + + /** + * Returns a simple string representation of the argument. + * Primarily for debugging. + */ + virtual std::string toString() const; + + /** + * Returns a short ID for the usage. + * \param valueId - The value used in the id. + */ + virtual std::string shortID( const std::string& valueId = "val" ) const; + + /** + * Returns a long ID for the usage. + * \param valueId - The value used in the id. + */ + virtual std::string longID( const std::string& valueId = "val" ) const; + + /** + * Trims a value off of the flag. + * \param flag - The string from which the flag and value will be + * trimmed. Contains the flag once the value has been trimmed. + * \param value - Where the value trimmed from the string will + * be stored. + */ + virtual void trimFlag( std::string& flag, std::string& value ) const; + + /** + * Checks whether a given string has blank chars, indicating that + * it is a combined SwitchArg. If so, return true, otherwise return + * false. + * \param s - string to be checked. + */ + bool _hasBlanks( const std::string& s ) const; + + /** + * Sets the requireLabel. Used by XorHandler. You shouldn't ever + * use this. + * \param s - Set the requireLabel to this value. + */ + void setRequireLabel( const std::string& s ); + + /** + * Used for MultiArgs and XorHandler to determine whether args + * can still be set. + */ + virtual bool allowMore(); + + /** + * Use by output classes to determine whether an Arg accepts + * multiple values. + */ + virtual bool acceptsMultipleValues(); + + /** + * Clears the Arg object and allows it to be reused by new + * command lines. + */ + virtual void reset(); +}; + +/** + * Typedef of an Arg list iterator. + */ +typedef std::list::iterator ArgListIterator; + +/** + * Typedef of an Arg vector iterator. + */ +typedef std::vector::iterator ArgVectorIterator; + +/** + * Typedef of a Visitor list iterator. + */ +typedef std::list::iterator VisitorListIterator; + +/* + * Extract a value of type T from it's string representation contained + * in strVal. The ValueLike parameter used to select the correct + * specialization of ExtractValue depending on the value traits of T. + * ValueLike traits use operator>> to assign the value from strVal. + */ +template void +ExtractValue(T &destVal, const std::string& strVal, ValueLike vl) +{ + static_cast(vl); // Avoid warning about unused vl + std::istringstream is(strVal); + + int valuesRead = 0; + while ( is.good() ) { + if ( is.peek() != EOF ) +#ifdef TCLAP_SETBASE_ZERO + is >> std::setbase(0) >> destVal; +#else + is >> destVal; +#endif + else + break; + + valuesRead++; + } + + if ( is.fail() ) + throw( ArgParseException("Couldn't read argument value " + "from string '" + strVal + "'")); + + + if ( valuesRead > 1 ) + throw( ArgParseException("More than one valid value parsed from " + "string '" + strVal + "'")); + +} + +/* + * Extract a value of type T from it's string representation contained + * in strVal. The ValueLike parameter used to select the correct + * specialization of ExtractValue depending on the value traits of T. + * StringLike uses assignment (operator=) to assign from strVal. + */ +template void +ExtractValue(T &destVal, const std::string& strVal, StringLike sl) +{ + static_cast(sl); // Avoid warning about unused sl + SetString(destVal, strVal); +} + +////////////////////////////////////////////////////////////////////// +//BEGIN Arg.cpp +////////////////////////////////////////////////////////////////////// + +inline Arg::Arg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + bool valreq, + Visitor* v) : + _flag(flag), + _name(name), + _description(desc), + _required(req), + _requireLabel("required"), + _valueRequired(valreq), + _alreadySet(false), + _visitor( v ), + _ignoreable(true), + _xorSet(false), + _acceptsMultipleValues(false) +{ + if ( _flag.length() > 1 ) + throw(SpecificationException( + "Argument flag can only be one character long", toString() ) ); + + if ( _name != ignoreNameString() && + ( _flag == Arg::flagStartString() || + _flag == Arg::nameStartString() || + _flag == " " ) ) + throw(SpecificationException("Argument flag cannot be either '" + + Arg::flagStartString() + "' or '" + + Arg::nameStartString() + "' or a space.", + toString() ) ); + + if ( ( _name.substr( 0, Arg::flagStartString().length() ) == Arg::flagStartString() ) || + ( _name.substr( 0, Arg::nameStartString().length() ) == Arg::nameStartString() ) || + ( _name.find( " ", 0 ) != std::string::npos ) ) + throw(SpecificationException("Argument name begin with either '" + + Arg::flagStartString() + "' or '" + + Arg::nameStartString() + "' or space.", + toString() ) ); + +} + +inline Arg::~Arg() { } + +inline std::string Arg::shortID( const std::string& valueId ) const +{ + std::string id = ""; + + if ( _flag != "" ) + id = Arg::flagStartString() + _flag; + else + id = Arg::nameStartString() + _name; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + if ( !_required ) + id = "[" + id + "]"; + + return id; +} + +inline std::string Arg::longID( const std::string& valueId ) const +{ + std::string id = ""; + + if ( _flag != "" ) + { + id += Arg::flagStartString() + _flag; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + id += ", "; + } + + id += Arg::nameStartString() + _name; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + return id; + +} + +inline bool Arg::operator==(const Arg& a) const +{ + if ( ( _flag != "" && _flag == a._flag ) || _name == a._name) + return true; + else + return false; +} + +inline std::string Arg::getDescription() const +{ + std::string desc = ""; + if ( _required ) + desc = "(" + _requireLabel + ") "; + +// if ( _valueRequired ) +// desc += "(value required) "; + + desc += _description; + return desc; +} + +inline const std::string& Arg::getFlag() const { return _flag; } + +inline const std::string& Arg::getName() const { return _name; } + +inline bool Arg::isRequired() const { return _required; } + +inline bool Arg::isValueRequired() const { return _valueRequired; } + +inline bool Arg::isSet() const +{ + if ( _alreadySet && !_xorSet ) + return true; + else + return false; +} + +inline bool Arg::isIgnoreable() const { return _ignoreable; } + +inline void Arg::setRequireLabel( const std::string& s) +{ + _requireLabel = s; +} + +inline bool Arg::argMatches( const std::string& argFlag ) const +{ + if ( ( argFlag == Arg::flagStartString() + _flag && _flag != "" ) || + argFlag == Arg::nameStartString() + _name ) + return true; + else + return false; +} + +inline std::string Arg::toString() const +{ + std::string s = ""; + + if ( _flag != "" ) + s += Arg::flagStartString() + _flag + " "; + + s += "(" + Arg::nameStartString() + _name + ")"; + + return s; +} + +inline void Arg::_checkWithVisitor() const +{ + if ( _visitor != NULL ) + _visitor->visit(); +} + +/** + * Implementation of trimFlag. + */ +inline void Arg::trimFlag(std::string& flag, std::string& value) const +{ + int stop = 0; + for ( int i = 0; static_cast(i) < flag.length(); i++ ) + if ( flag[i] == Arg::delimiter() ) + { + stop = i; + break; + } + + if ( stop > 1 ) + { + value = flag.substr(stop+1); + flag = flag.substr(0,stop); + } + +} + +/** + * Implementation of _hasBlanks. + */ +inline bool Arg::_hasBlanks( const std::string& s ) const +{ + for ( int i = 1; static_cast(i) < s.length(); i++ ) + if ( s[i] == Arg::blankChar() ) + return true; + + return false; +} + +inline void Arg::forceRequired() +{ + _required = true; +} + +inline void Arg::xorSet() +{ + _alreadySet = true; + _xorSet = true; +} + +/** + * Overridden by Args that need to added to the end of the list. + */ +inline void Arg::addToList( std::list& argList ) const +{ + argList.push_front( const_cast(this) ); +} + +inline bool Arg::allowMore() +{ + return false; +} + +inline bool Arg::acceptsMultipleValues() +{ + return _acceptsMultipleValues; +} + +inline void Arg::reset() +{ + _xorSet = false; + _alreadySet = false; +} + +////////////////////////////////////////////////////////////////////// +//END Arg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif + diff --git a/components/mkspiffs/src/tclap/ArgException.h b/components/mkspiffs/src/tclap/ArgException.h new file mode 100644 index 0000000..3411aa9 --- /dev/null +++ b/components/mkspiffs/src/tclap/ArgException.h @@ -0,0 +1,200 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ArgException.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_ARG_EXCEPTION_H +#define TCLAP_ARG_EXCEPTION_H + +#include +#include + +namespace TCLAP { + +/** + * A simple class that defines and argument exception. Should be caught + * whenever a CmdLine is created and parsed. + */ +class ArgException : public std::exception +{ + public: + + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source. + * \param td - Text describing the type of ArgException it is. + * of the exception. + */ + ArgException( const std::string& text = "undefined exception", + const std::string& id = "undefined", + const std::string& td = "Generic ArgException") + : std::exception(), + _errorText(text), + _argId( id ), + _typeDescription(td) + { } + + /** + * Destructor. + */ + virtual ~ArgException() throw() { } + + /** + * Returns the error text. + */ + std::string error() const { return ( _errorText ); } + + /** + * Returns the argument id. + */ + std::string argId() const + { + if ( _argId == "undefined" ) + return " "; + else + return ( "Argument: " + _argId ); + } + + /** + * Returns the arg id and error text. + */ + const char* what() const throw() + { + static std::string ex; + ex = _argId + " -- " + _errorText; + return ex.c_str(); + } + + /** + * Returns the type of the exception. Used to explain and distinguish + * between different child exceptions. + */ + std::string typeDescription() const + { + return _typeDescription; + } + + + private: + + /** + * The text of the exception message. + */ + std::string _errorText; + + /** + * The argument related to this exception. + */ + std::string _argId; + + /** + * Describes the type of the exception. Used to distinguish + * between different child exceptions. + */ + std::string _typeDescription; + +}; + +/** + * Thrown from within the child Arg classes when it fails to properly + * parse the argument it has been passed. + */ +class ArgParseException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + ArgParseException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string( "Exception found while parsing " ) + + std::string( "the value the Arg has been passed." )) + { } +}; + +/** + * Thrown from CmdLine when the arguments on the command line are not + * properly specified, e.g. too many arguments, required argument missing, etc. + */ +class CmdLineParseException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + CmdLineParseException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string( "Exception found when the values ") + + std::string( "on the command line do not meet ") + + std::string( "the requirements of the defined ") + + std::string( "Args." )) + { } +}; + +/** + * Thrown from Arg and CmdLine when an Arg is improperly specified, e.g. + * same flag as another Arg, same name, etc. + */ +class SpecificationException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + SpecificationException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string("Exception found when an Arg object ")+ + std::string("is improperly defined by the ") + + std::string("developer." )) + { } + +}; + +class ExitException { +public: + ExitException(int estat) : _estat(estat) {} + + int getExitStatus() const { return _estat; } + +private: + int _estat; +}; + +} // namespace TCLAP + +#endif + diff --git a/components/mkspiffs/src/tclap/ArgTraits.h b/components/mkspiffs/src/tclap/ArgTraits.h new file mode 100644 index 0000000..0b2c18f --- /dev/null +++ b/components/mkspiffs/src/tclap/ArgTraits.h @@ -0,0 +1,87 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ArgTraits.h + * + * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +// This is an internal tclap file, you should probably not have to +// include this directly + +#ifndef TCLAP_ARGTRAITS_H +#define TCLAP_ARGTRAITS_H + +namespace TCLAP { + +// We use two empty structs to get compile type specialization +// function to work + +/** + * A value like argument value type is a value that can be set using + * operator>>. This is the default value type. + */ +struct ValueLike { + typedef ValueLike ValueCategory; + virtual ~ValueLike() {} +}; + +/** + * A string like argument value type is a value that can be set using + * operator=(string). Usefull if the value type contains spaces which + * will be broken up into individual tokens by operator>>. + */ +struct StringLike { + virtual ~StringLike() {} +}; + +/** + * A class can inherit from this object to make it have string like + * traits. This is a compile time thing and does not add any overhead + * to the inherenting class. + */ +struct StringLikeTrait { + typedef StringLike ValueCategory; + virtual ~StringLikeTrait() {} +}; + +/** + * A class can inherit from this object to make it have value like + * traits. This is a compile time thing and does not add any overhead + * to the inherenting class. + */ +struct ValueLikeTrait { + typedef ValueLike ValueCategory; + virtual ~ValueLikeTrait() {} +}; + +/** + * Arg traits are used to get compile type specialization when parsing + * argument values. Using an ArgTraits you can specify the way that + * values gets assigned to any particular type during parsing. The two + * supported types are StringLike and ValueLike. + */ +template +struct ArgTraits { + typedef typename T::ValueCategory ValueCategory; + virtual ~ArgTraits() {} + //typedef ValueLike ValueCategory; +}; + +#endif + +} // namespace diff --git a/components/mkspiffs/src/tclap/COPYING b/components/mkspiffs/src/tclap/COPYING new file mode 100644 index 0000000..987be0c --- /dev/null +++ b/components/mkspiffs/src/tclap/COPYING @@ -0,0 +1,25 @@ + + +Copyright (c) 2003 Michael E. Smoot + +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. + + diff --git a/components/mkspiffs/src/tclap/CmdLine.h b/components/mkspiffs/src/tclap/CmdLine.h new file mode 100644 index 0000000..0e7dda3 --- /dev/null +++ b/components/mkspiffs/src/tclap/CmdLine.h @@ -0,0 +1,633 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: CmdLine.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_CMDLINE_H +#define TCLAP_CMDLINE_H + +#include "SwitchArg.h" +#include "MultiSwitchArg.h" +#include "UnlabeledValueArg.h" +#include "UnlabeledMultiArg.h" + +#include "XorHandler.h" +#include "HelpVisitor.h" +#include "VersionVisitor.h" +#include "IgnoreRestVisitor.h" + +#include "CmdLineOutput.h" +#include "StdOutput.h" + +#include "Constraint.h" +#include "ValuesConstraint.h" + +#include +#include +#include +#include +#include +#include +#include // Needed for exit(), which isn't defined in some envs. + +namespace TCLAP { + +template void DelPtr(T ptr) +{ + delete ptr; +} + +template void ClearContainer(C &c) +{ + typedef typename C::value_type value_type; + std::for_each(c.begin(), c.end(), DelPtr); + c.clear(); +} + + +/** + * The base class that manages the command line definition and passes + * along the parsing to the appropriate Arg classes. + */ +class CmdLine : public CmdLineInterface +{ + protected: + + /** + * The list of arguments that will be tested against the + * command line. + */ + std::list _argList; + + /** + * The name of the program. Set to argv[0]. + */ + std::string _progName; + + /** + * A message used to describe the program. Used in the usage output. + */ + std::string _message; + + /** + * The version to be displayed with the --version switch. + */ + std::string _version; + + /** + * The number of arguments that are required to be present on + * the command line. This is set dynamically, based on the + * Args added to the CmdLine object. + */ + int _numRequired; + + /** + * The character that is used to separate the argument flag/name + * from the value. Defaults to ' ' (space). + */ + char _delimiter; + + /** + * The handler that manages xoring lists of args. + */ + XorHandler _xorHandler; + + /** + * A list of Args to be explicitly deleted when the destructor + * is called. At the moment, this only includes the three default + * Args. + */ + std::list _argDeleteOnExitList; + + /** + * A list of Visitors to be explicitly deleted when the destructor + * is called. At the moment, these are the Vistors created for the + * default Args. + */ + std::list _visitorDeleteOnExitList; + + /** + * Object that handles all output for the CmdLine. + */ + CmdLineOutput* _output; + + /** + * Should CmdLine handle parsing exceptions internally? + */ + bool _handleExceptions; + + /** + * Throws an exception listing the missing args. + */ + void missingArgsException(); + + /** + * Checks whether a name/flag string matches entirely matches + * the Arg::blankChar. Used when multiple switches are combined + * into a single argument. + * \param s - The message to be used in the usage. + */ + bool _emptyCombined(const std::string& s); + + /** + * Perform a delete ptr; operation on ptr when this object is deleted. + */ + void deleteOnExit(Arg* ptr); + + /** + * Perform a delete ptr; operation on ptr when this object is deleted. + */ + void deleteOnExit(Visitor* ptr); + +private: + + /** + * Prevent accidental copying. + */ + CmdLine(const CmdLine& rhs); + CmdLine& operator=(const CmdLine& rhs); + + /** + * Encapsulates the code common to the constructors + * (which is all of it). + */ + void _constructor(); + + + /** + * Is set to true when a user sets the output object. We use this so + * that we don't delete objects that are created outside of this lib. + */ + bool _userSetOutput; + + /** + * Whether or not to automatically create help and version switches. + */ + bool _helpAndVersion; + + public: + + /** + * Command line constructor. Defines how the arguments will be + * parsed. + * \param message - The message to be used in the usage + * output. + * \param delimiter - The character that is used to separate + * the argument flag/name from the value. Defaults to ' ' (space). + * \param version - The version number to be used in the + * --version switch. + * \param helpAndVersion - Whether or not to create the Help and + * Version switches. Defaults to true. + */ + CmdLine(const std::string& message, + const char delimiter = ' ', + const std::string& version = "none", + bool helpAndVersion = true); + + /** + * Deletes any resources allocated by a CmdLine object. + */ + virtual ~CmdLine(); + + /** + * Adds an argument to the list of arguments to be parsed. + * \param a - Argument to be added. + */ + void add( Arg& a ); + + /** + * An alternative add. Functionally identical. + * \param a - Argument to be added. + */ + void add( Arg* a ); + + /** + * Add two Args that will be xor'd. If this method is used, add does + * not need to be called. + * \param a - Argument to be added and xor'd. + * \param b - Argument to be added and xor'd. + */ + void xorAdd( Arg& a, Arg& b ); + + /** + * Add a list of Args that will be xor'd. If this method is used, + * add does not need to be called. + * \param xors - List of Args to be added and xor'd. + */ + void xorAdd( std::vector& xors ); + + /** + * Parses the command line. + * \param argc - Number of arguments. + * \param argv - Array of arguments. + */ + void parse(int argc, const char * const * argv); + + /** + * Parses the command line. + * \param args - A vector of strings representing the args. + * args[0] is still the program name. + */ + void parse(std::vector& args); + + /** + * + */ + CmdLineOutput* getOutput(); + + /** + * + */ + void setOutput(CmdLineOutput* co); + + /** + * + */ + std::string& getVersion(); + + /** + * + */ + std::string& getProgramName(); + + /** + * + */ + std::list& getArgList(); + + /** + * + */ + XorHandler& getXorHandler(); + + /** + * + */ + char getDelimiter(); + + /** + * + */ + std::string& getMessage(); + + /** + * + */ + bool hasHelpAndVersion(); + + /** + * Disables or enables CmdLine's internal parsing exception handling. + * + * @param state Should CmdLine handle parsing exceptions internally? + */ + void setExceptionHandling(const bool state); + + /** + * Returns the current state of the internal exception handling. + * + * @retval true Parsing exceptions are handled internally. + * @retval false Parsing exceptions are propagated to the caller. + */ + bool getExceptionHandling() const; + + /** + * Allows the CmdLine object to be reused. + */ + void reset(); + +}; + + +/////////////////////////////////////////////////////////////////////////////// +//Begin CmdLine.cpp +/////////////////////////////////////////////////////////////////////////////// + +inline CmdLine::CmdLine(const std::string& m, + char delim, + const std::string& v, + bool help ) + : + _argList(std::list()), + _progName("not_set_yet"), + _message(m), + _version(v), + _numRequired(0), + _delimiter(delim), + _xorHandler(XorHandler()), + _argDeleteOnExitList(std::list()), + _visitorDeleteOnExitList(std::list()), + _output(0), + _handleExceptions(true), + _userSetOutput(false), + _helpAndVersion(help) +{ + _constructor(); +} + +inline CmdLine::~CmdLine() +{ + ClearContainer(_argDeleteOnExitList); + ClearContainer(_visitorDeleteOnExitList); + + if ( !_userSetOutput ) { + delete _output; + _output = 0; + } +} + +inline void CmdLine::_constructor() +{ + _output = new StdOutput; + + Arg::setDelimiter( _delimiter ); + + Visitor* v; + + if ( _helpAndVersion ) + { + v = new HelpVisitor( this, &_output ); + SwitchArg* help = new SwitchArg("h","help", + "Displays usage information and exits.", + false, v); + add( help ); + deleteOnExit(help); + deleteOnExit(v); + + v = new VersionVisitor( this, &_output ); + SwitchArg* vers = new SwitchArg("","version", + "Displays version information and exits.", + false, v); + add( vers ); + deleteOnExit(vers); + deleteOnExit(v); + } + + v = new IgnoreRestVisitor(); + SwitchArg* ignore = new SwitchArg(Arg::flagStartString(), + Arg::ignoreNameString(), + "Ignores the rest of the labeled arguments following this flag.", + false, v); + add( ignore ); + deleteOnExit(ignore); + deleteOnExit(v); +} + +inline void CmdLine::xorAdd( std::vector& ors ) +{ + _xorHandler.add( ors ); + + for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++) + { + (*it)->forceRequired(); + (*it)->setRequireLabel( "OR required" ); + add( *it ); + } +} + +inline void CmdLine::xorAdd( Arg& a, Arg& b ) +{ + std::vector ors; + ors.push_back( &a ); + ors.push_back( &b ); + xorAdd( ors ); +} + +inline void CmdLine::add( Arg& a ) +{ + add( &a ); +} + +inline void CmdLine::add( Arg* a ) +{ + for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) + if ( *a == *(*it) ) + throw( SpecificationException( + "Argument with same flag/name already exists!", + a->longID() ) ); + + a->addToList( _argList ); + + if ( a->isRequired() ) + _numRequired++; +} + + +inline void CmdLine::parse(int argc, const char * const * argv) +{ + // this step is necessary so that we have easy access to + // mutable strings. + std::vector args; + for (int i = 0; i < argc; i++) + args.push_back(argv[i]); + + parse(args); +} + +inline void CmdLine::parse(std::vector& args) +{ + bool shouldExit = false; + int estat = 0; + + try { + _progName = args.front(); + args.erase(args.begin()); + + int requiredCount = 0; + + for (int i = 0; static_cast(i) < args.size(); i++) + { + bool matched = false; + for (ArgListIterator it = _argList.begin(); + it != _argList.end(); it++) { + if ( (*it)->processArg( &i, args ) ) + { + requiredCount += _xorHandler.check( *it ); + matched = true; + break; + } + } + + // checks to see if the argument is an empty combined + // switch and if so, then we've actually matched it + if ( !matched && _emptyCombined( args[i] ) ) + matched = true; + + if ( !matched && !Arg::ignoreRest() ) + throw(CmdLineParseException("Couldn't find match " + "for argument", + args[i])); + } + + if ( requiredCount < _numRequired ) + missingArgsException(); + + if ( requiredCount > _numRequired ) + throw(CmdLineParseException("Too many arguments!")); + + } catch ( ArgException& e ) { + // If we're not handling the exceptions, rethrow. + if ( !_handleExceptions) { + throw; + } + + try { + _output->failure(*this,e); + } catch ( ExitException &ee ) { + estat = ee.getExitStatus(); + shouldExit = true; + } + } catch (ExitException &ee) { + // If we're not handling the exceptions, rethrow. + if ( !_handleExceptions) { + throw; + } + + estat = ee.getExitStatus(); + shouldExit = true; + } + + if (shouldExit) + exit(estat); +} + +inline bool CmdLine::_emptyCombined(const std::string& s) +{ + if ( s.length() > 0 && s[0] != Arg::flagStartChar() ) + return false; + + for ( int i = 1; static_cast(i) < s.length(); i++ ) + if ( s[i] != Arg::blankChar() ) + return false; + + return true; +} + +inline void CmdLine::missingArgsException() +{ + int count = 0; + + std::string missingArgList; + for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++) + { + if ( (*it)->isRequired() && !(*it)->isSet() ) + { + missingArgList += (*it)->getName(); + missingArgList += ", "; + count++; + } + } + missingArgList = missingArgList.substr(0,missingArgList.length()-2); + + std::string msg; + if ( count > 1 ) + msg = "Required arguments missing: "; + else + msg = "Required argument missing: "; + + msg += missingArgList; + + throw(CmdLineParseException(msg)); +} + +inline void CmdLine::deleteOnExit(Arg* ptr) +{ + _argDeleteOnExitList.push_back(ptr); +} + +inline void CmdLine::deleteOnExit(Visitor* ptr) +{ + _visitorDeleteOnExitList.push_back(ptr); +} + +inline CmdLineOutput* CmdLine::getOutput() +{ + return _output; +} + +inline void CmdLine::setOutput(CmdLineOutput* co) +{ + if ( !_userSetOutput ) + delete _output; + _userSetOutput = true; + _output = co; +} + +inline std::string& CmdLine::getVersion() +{ + return _version; +} + +inline std::string& CmdLine::getProgramName() +{ + return _progName; +} + +inline std::list& CmdLine::getArgList() +{ + return _argList; +} + +inline XorHandler& CmdLine::getXorHandler() +{ + return _xorHandler; +} + +inline char CmdLine::getDelimiter() +{ + return _delimiter; +} + +inline std::string& CmdLine::getMessage() +{ + return _message; +} + +inline bool CmdLine::hasHelpAndVersion() +{ + return _helpAndVersion; +} + +inline void CmdLine::setExceptionHandling(const bool state) +{ + _handleExceptions = state; +} + +inline bool CmdLine::getExceptionHandling() const +{ + return _handleExceptions; +} + +inline void CmdLine::reset() +{ + for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) + (*it)->reset(); + + _progName.clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +//End CmdLine.cpp +/////////////////////////////////////////////////////////////////////////////// + + + +} //namespace TCLAP +#endif diff --git a/components/mkspiffs/src/tclap/CmdLineInterface.h b/components/mkspiffs/src/tclap/CmdLineInterface.h new file mode 100644 index 0000000..1b25e9b --- /dev/null +++ b/components/mkspiffs/src/tclap/CmdLineInterface.h @@ -0,0 +1,150 @@ + +/****************************************************************************** + * + * file: CmdLineInterface.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_COMMANDLINE_INTERFACE_H +#define TCLAP_COMMANDLINE_INTERFACE_H + +#include +#include +#include +#include +#include + + +namespace TCLAP { + +class Arg; +class CmdLineOutput; +class XorHandler; + +/** + * The base class that manages the command line definition and passes + * along the parsing to the appropriate Arg classes. + */ +class CmdLineInterface +{ + public: + + /** + * Destructor + */ + virtual ~CmdLineInterface() {} + + /** + * Adds an argument to the list of arguments to be parsed. + * \param a - Argument to be added. + */ + virtual void add( Arg& a )=0; + + /** + * An alternative add. Functionally identical. + * \param a - Argument to be added. + */ + virtual void add( Arg* a )=0; + + /** + * Add two Args that will be xor'd. + * If this method is used, add does + * not need to be called. + * \param a - Argument to be added and xor'd. + * \param b - Argument to be added and xor'd. + */ + virtual void xorAdd( Arg& a, Arg& b )=0; + + /** + * Add a list of Args that will be xor'd. If this method is used, + * add does not need to be called. + * \param xors - List of Args to be added and xor'd. + */ + virtual void xorAdd( std::vector& xors )=0; + + /** + * Parses the command line. + * \param argc - Number of arguments. + * \param argv - Array of arguments. + */ + virtual void parse(int argc, const char * const * argv)=0; + + /** + * Parses the command line. + * \param args - A vector of strings representing the args. + * args[0] is still the program name. + */ + void parse(std::vector& args); + + /** + * Returns the CmdLineOutput object. + */ + virtual CmdLineOutput* getOutput()=0; + + /** + * \param co - CmdLineOutput object that we want to use instead. + */ + virtual void setOutput(CmdLineOutput* co)=0; + + /** + * Returns the version string. + */ + virtual std::string& getVersion()=0; + + /** + * Returns the program name string. + */ + virtual std::string& getProgramName()=0; + + /** + * Returns the argList. + */ + virtual std::list& getArgList()=0; + + /** + * Returns the XorHandler. + */ + virtual XorHandler& getXorHandler()=0; + + /** + * Returns the delimiter string. + */ + virtual char getDelimiter()=0; + + /** + * Returns the message string. + */ + virtual std::string& getMessage()=0; + + /** + * Indicates whether or not the help and version switches were created + * automatically. + */ + virtual bool hasHelpAndVersion()=0; + + /** + * Resets the instance as if it had just been constructed so that the + * instance can be reused. + */ + virtual void reset()=0; +}; + +} //namespace + + +#endif diff --git a/components/mkspiffs/src/tclap/CmdLineOutput.h b/components/mkspiffs/src/tclap/CmdLineOutput.h new file mode 100644 index 0000000..71ee5a3 --- /dev/null +++ b/components/mkspiffs/src/tclap/CmdLineOutput.h @@ -0,0 +1,74 @@ + + +/****************************************************************************** + * + * file: CmdLineOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_CMDLINEOUTPUT_H +#define TCLAP_CMDLINEOUTPUT_H + +#include +#include +#include +#include +#include +#include + +namespace TCLAP { + +class CmdLineInterface; +class ArgException; + +/** + * The interface that any output object must implement. + */ +class CmdLineOutput +{ + + public: + + /** + * Virtual destructor. + */ + virtual ~CmdLineOutput() {} + + /** + * Generates some sort of output for the USAGE. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c)=0; + + /** + * Generates some sort of output for the version. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c)=0; + + /** + * Generates some sort of output for a failure. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure( CmdLineInterface& c, + ArgException& e )=0; + +}; + +} //namespace TCLAP +#endif diff --git a/components/mkspiffs/src/tclap/Constraint.h b/components/mkspiffs/src/tclap/Constraint.h new file mode 100644 index 0000000..a92acf9 --- /dev/null +++ b/components/mkspiffs/src/tclap/Constraint.h @@ -0,0 +1,68 @@ + +/****************************************************************************** + * + * file: Constraint.h + * + * Copyright (c) 2005, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_CONSTRAINT_H +#define TCLAP_CONSTRAINT_H + +#include +#include +#include +#include +#include +#include + +namespace TCLAP { + +/** + * The interface that defines the interaction between the Arg and Constraint. + */ +template +class Constraint +{ + + public: + /** + * Returns a description of the Constraint. + */ + virtual std::string description() const =0; + + /** + * Returns the short ID for the Constraint. + */ + virtual std::string shortID() const =0; + + /** + * The method used to verify that the value parsed from the command + * line meets the constraint. + * \param value - The value that will be checked. + */ + virtual bool check(const T& value) const =0; + + /** + * Destructor. + * Silences warnings about Constraint being a base class with virtual + * functions but without a virtual destructor. + */ + virtual ~Constraint() { ; } +}; + +} //namespace TCLAP +#endif diff --git a/components/mkspiffs/src/tclap/DocBookOutput.h b/components/mkspiffs/src/tclap/DocBookOutput.h new file mode 100644 index 0000000..84abf46 --- /dev/null +++ b/components/mkspiffs/src/tclap/DocBookOutput.h @@ -0,0 +1,299 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: DocBookOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_DOCBOOKOUTPUT_H +#define TCLAP_DOCBOOKOUTPUT_H + +#include +#include +#include +#include +#include + +#include "CmdLineInterface.h" +#include "CmdLineOutput.h" +#include "XorHandler.h" +#include "Arg.h" + +namespace TCLAP { + +/** + * A class that generates DocBook output for usage() method for the + * given CmdLine and its Args. + */ +class DocBookOutput : public CmdLineOutput +{ + + public: + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + /** + * Substitutes the char r for string x in string s. + * \param s - The string to operate on. + * \param r - The char to replace. + * \param x - What to replace r with. + */ + void substituteSpecialChars( std::string& s, char r, std::string& x ); + void removeChar( std::string& s, char r); + void basename( std::string& s ); + + void printShortArg(Arg* it); + void printLongArg(Arg* it); + + char theDelimiter; +}; + + +inline void DocBookOutput::version(CmdLineInterface& _cmd) +{ + std::cout << _cmd.getVersion() << std::endl; +} + +inline void DocBookOutput::usage(CmdLineInterface& _cmd ) +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + theDelimiter = _cmd.getDelimiter(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + basename(progName); + + std::cout << "" << std::endl; + std::cout << "" << std::endl << std::endl; + + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << progName << "" << std::endl; + std::cout << "1" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << progName << "" << std::endl; + std::cout << "" << _cmd.getMessage() << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << progName << "" << std::endl; + + // xor + for ( int i = 0; (unsigned int)i < xorList.size(); i++ ) + { + std::cout << "" << std::endl; + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); it++ ) + printShortArg((*it)); + + std::cout << "" << std::endl; + } + + // rest of args + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + printShortArg((*it)); + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Description" << std::endl; + std::cout << "" << std::endl; + std::cout << _cmd.getMessage() << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Options" << std::endl; + + std::cout << "" << std::endl; + + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + printLongArg((*it)); + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Version" << std::endl; + std::cout << "" << std::endl; + std::cout << xversion << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + +} + +inline void DocBookOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + static_cast(_cmd); // unused + std::cout << e.what() << std::endl; + throw ExitException(1); +} + +inline void DocBookOutput::substituteSpecialChars( std::string& s, + char r, + std::string& x ) +{ + size_t p; + while ( (p = s.find_first_of(r)) != std::string::npos ) + { + s.erase(p,1); + s.insert(p,x); + } +} + +inline void DocBookOutput::removeChar( std::string& s, char r) +{ + size_t p; + while ( (p = s.find_first_of(r)) != std::string::npos ) + { + s.erase(p,1); + } +} + +inline void DocBookOutput::basename( std::string& s ) +{ + size_t p = s.find_last_of('/'); + if ( p != std::string::npos ) + { + s.erase(0, p + 1); + } +} + +inline void DocBookOutput::printShortArg(Arg* a) +{ + std::string lt = "<"; + std::string gt = ">"; + + std::string id = a->shortID(); + substituteSpecialChars(id,'<',lt); + substituteSpecialChars(id,'>',gt); + removeChar(id,'['); + removeChar(id,']'); + + std::string choice = "opt"; + if ( a->isRequired() ) + choice = "plain"; + + std::cout << "acceptsMultipleValues() ) + std::cout << " rep='repeat'"; + + + std::cout << '>'; + if ( !a->getFlag().empty() ) + std::cout << a->flagStartChar() << a->getFlag(); + else + std::cout << a->nameStartString() << a->getName(); + if ( a->isValueRequired() ) + { + std::string arg = a->shortID(); + removeChar(arg,'['); + removeChar(arg,']'); + removeChar(arg,'<'); + removeChar(arg,'>'); + arg.erase(0, arg.find_last_of(theDelimiter) + 1); + std::cout << theDelimiter; + std::cout << "" << arg << ""; + } + std::cout << "" << std::endl; + +} + +inline void DocBookOutput::printLongArg(Arg* a) +{ + std::string lt = "<"; + std::string gt = ">"; + + std::string desc = a->getDescription(); + substituteSpecialChars(desc,'<',lt); + substituteSpecialChars(desc,'>',gt); + + std::cout << "" << std::endl; + + if ( !a->getFlag().empty() ) + { + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + } + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << desc << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; +} + +} //namespace TCLAP +#endif diff --git a/components/mkspiffs/src/tclap/HelpVisitor.h b/components/mkspiffs/src/tclap/HelpVisitor.h new file mode 100644 index 0000000..076c640 --- /dev/null +++ b/components/mkspiffs/src/tclap/HelpVisitor.h @@ -0,0 +1,76 @@ + +/****************************************************************************** + * + * file: HelpVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_HELP_VISITOR_H +#define TCLAP_HELP_VISITOR_H + +#include "CmdLineInterface.h" +#include "CmdLineOutput.h" +#include "Visitor.h" + +namespace TCLAP { + +/** + * A Visitor object that calls the usage method of the given CmdLineOutput + * object for the specified CmdLine object. + */ +class HelpVisitor: public Visitor +{ + private: + /** + * Prevent accidental copying. + */ + HelpVisitor(const HelpVisitor& rhs); + HelpVisitor& operator=(const HelpVisitor& rhs); + + protected: + + /** + * The CmdLine the output will be generated for. + */ + CmdLineInterface* _cmd; + + /** + * The output object. + */ + CmdLineOutput** _out; + + public: + + /** + * Constructor. + * \param cmd - The CmdLine the output will be generated for. + * \param out - The type of output. + */ + HelpVisitor(CmdLineInterface* cmd, CmdLineOutput** out) + : Visitor(), _cmd( cmd ), _out( out ) { } + + /** + * Calls the usage method of the CmdLineOutput for the + * specified CmdLine. + */ + void visit() { (*_out)->usage(*_cmd); throw ExitException(0); } + +}; + +} + +#endif diff --git a/components/mkspiffs/src/tclap/IgnoreRestVisitor.h b/components/mkspiffs/src/tclap/IgnoreRestVisitor.h new file mode 100644 index 0000000..09bdba7 --- /dev/null +++ b/components/mkspiffs/src/tclap/IgnoreRestVisitor.h @@ -0,0 +1,52 @@ + +/****************************************************************************** + * + * file: IgnoreRestVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_IGNORE_REST_VISITOR_H +#define TCLAP_IGNORE_REST_VISITOR_H + +#include "Visitor.h" +#include "Arg.h" + +namespace TCLAP { + +/** + * A Vistor that tells the CmdLine to begin ignoring arguments after + * this one is parsed. + */ +class IgnoreRestVisitor: public Visitor +{ + public: + + /** + * Constructor. + */ + IgnoreRestVisitor() : Visitor() {} + + /** + * Sets Arg::_ignoreRest. + */ + void visit() { Arg::beginIgnoring(); } +}; + +} + +#endif diff --git a/components/mkspiffs/src/tclap/MultiArg.h b/components/mkspiffs/src/tclap/MultiArg.h new file mode 100644 index 0000000..63c88c7 --- /dev/null +++ b/components/mkspiffs/src/tclap/MultiArg.h @@ -0,0 +1,433 @@ +/****************************************************************************** + * + * file: MultiArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_MULTIPLE_ARGUMENT_H +#define TCLAP_MULTIPLE_ARGUMENT_H + +#include +#include + +#include "Arg.h" +#include "Constraint.h" + +namespace TCLAP { +/** + * An argument that allows multiple values of type T to be specified. Very + * similar to a ValueArg, except a vector of values will be returned + * instead of just one. + */ +template +class MultiArg : public Arg +{ +public: + typedef std::vector container_type; + typedef typename container_type::iterator iterator; + typedef typename container_type::const_iterator const_iterator; + +protected: + + /** + * The list of values parsed from the CmdLine. + */ + std::vector _values; + + /** + * The description of type T to be used in the usage. + */ + std::string _typeDesc; + + /** + * A list of constraint on this Arg. + */ + Constraint* _constraint; + + /** + * Extracts the value from the string. + * Attempts to parse string as type T, if this fails an exception + * is thrown. + * \param val - The string to be read. + */ + void _extractValue( const std::string& val ); + + /** + * Used by XorHandler to decide whether to keep parsing for this arg. + */ + bool _allowMore; + +public: + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + Visitor* v = NULL); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + Visitor* v = NULL ); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns a vector of type T containing the values parsed from + * the command line. + */ + const std::vector& getValue(); + + /** + * Returns an iterator over the values parsed from the command + * line. + */ + const_iterator begin() const { return _values.begin(); } + + /** + * Returns the end of the values parsed from the command + * line. + */ + const_iterator end() const { return _values.end(); } + + /** + * Returns the a short id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Returns the a long id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Once we've matched the first value, then the arg is no longer + * required. + */ + virtual bool isRequired() const; + + virtual bool allowMore(); + + virtual void reset(); + +private: + /** + * Prevent accidental copying + */ + MultiArg(const MultiArg& rhs); + MultiArg& operator=(const MultiArg& rhs); + +}; + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + Visitor* v) : + Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( typeDesc ), + _constraint( NULL ), + _allowMore(false) +{ + _acceptsMultipleValues = true; +} + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( typeDesc ), + _constraint( NULL ), + _allowMore(false) +{ + parser.add( this ); + _acceptsMultipleValues = true; +} + +/** + * + */ +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( constraint->shortID() ), + _constraint( constraint ), + _allowMore(false) +{ + _acceptsMultipleValues = true; +} + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( constraint->shortID() ), + _constraint( constraint ), + _allowMore(false) +{ + parser.add( this ); + _acceptsMultipleValues = true; +} + +template +const std::vector& MultiArg::getValue() { return _values; } + +template +bool MultiArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + std::string flag = args[*i]; + std::string value = ""; + + trimFlag( flag, value ); + + if ( argMatches( flag ) ) + { + if ( Arg::delimiter() != ' ' && value == "" ) + throw( ArgParseException( + "Couldn't find delimiter for this argument!", + toString() ) ); + + // always take the first one, regardless of start string + if ( value == "" ) + { + (*i)++; + if ( static_cast(*i) < args.size() ) + _extractValue( args[*i] ); + else + throw( ArgParseException("Missing a value for this argument!", + toString() ) ); + } + else + _extractValue( value ); + + /* + // continuing taking the args until we hit one with a start string + while ( (unsigned int)(*i)+1 < args.size() && + args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && + args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) + _extractValue( args[++(*i)] ); + */ + + _alreadySet = true; + _checkWithVisitor(); + + return true; + } + else + return false; +} + +/** + * + */ +template +std::string MultiArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::shortID(_typeDesc) + " ... "; +} + +/** + * + */ +template +std::string MultiArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::longID(_typeDesc) + " (accepted multiple times)"; +} + +/** + * Once we've matched the first value, then the arg is no longer + * required. + */ +template +bool MultiArg::isRequired() const +{ + if ( _required ) + { + if ( _values.size() > 1 ) + return false; + else + return true; + } + else + return false; + +} + +template +void MultiArg::_extractValue( const std::string& val ) +{ + try { + T tmp; + ExtractValue(tmp, val, typename ArgTraits::ValueCategory()); + _values.push_back(tmp); + } catch( ArgParseException &e) { + throw ArgParseException(e.error(), toString()); + } + + if ( _constraint != NULL ) + if ( ! _constraint->check( _values.back() ) ) + throw( CmdLineParseException( "Value '" + val + + "' does not meet constraint: " + + _constraint->description(), + toString() ) ); +} + +template +bool MultiArg::allowMore() +{ + bool am = _allowMore; + _allowMore = true; + return am; +} + +template +void MultiArg::reset() +{ + Arg::reset(); + _values.clear(); +} + +} // namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/MultiSwitchArg.h b/components/mkspiffs/src/tclap/MultiSwitchArg.h new file mode 100644 index 0000000..7661d85 --- /dev/null +++ b/components/mkspiffs/src/tclap/MultiSwitchArg.h @@ -0,0 +1,216 @@ + +/****************************************************************************** +* +* file: MultiSwitchArg.h +* +* Copyright (c) 2003, Michael E. Smoot . +* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. +* Copyright (c) 2005, Michael E. Smoot, Daniel Aarno, Erik Zeek. +* All rights reverved. +* +* See the file COPYING in the top directory of this distribution for +* more information. +* +* 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. +* +*****************************************************************************/ + + +#ifndef TCLAP_MULTI_SWITCH_ARG_H +#define TCLAP_MULTI_SWITCH_ARG_H + +#include +#include + +#include "SwitchArg.h" + +namespace TCLAP { + +/** +* A multiple switch argument. If the switch is set on the command line, then +* the getValue method will return the number of times the switch appears. +*/ +class MultiSwitchArg : public SwitchArg +{ + protected: + + /** + * The value of the switch. + */ + int _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + int _default; + + public: + + /** + * MultiSwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param init - Optional. The initial/default value of this Arg. + * Defaults to 0. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + int init = 0, + Visitor* v = NULL); + + + /** + * MultiSwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param parser - A CmdLine parser object to add this Arg to + * \param init - Optional. The initial/default value of this Arg. + * Defaults to 0. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + int init = 0, + Visitor* v = NULL); + + + /** + * Handles the processing of the argument. + * This re-implements the SwitchArg version of this method to set the + * _value of the argument appropriately. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns int, the number of times the switch has been set. + */ + int getValue(); + + /** + * Returns the shortID for this Arg. + */ + std::string shortID(const std::string& val) const; + + /** + * Returns the longID for this Arg. + */ + std::string longID(const std::string& val) const; + + void reset(); + +}; + +////////////////////////////////////////////////////////////////////// +//BEGIN MultiSwitchArg.cpp +////////////////////////////////////////////////////////////////////// +inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + int init, + Visitor* v ) +: SwitchArg(flag, name, desc, false, v), +_value( init ), +_default( init ) +{ } + +inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + int init, + Visitor* v ) +: SwitchArg(flag, name, desc, false, v), +_value( init ), +_default( init ) +{ + parser.add( this ); +} + +inline int MultiSwitchArg::getValue() { return _value; } + +inline bool MultiSwitchArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( argMatches( args[*i] )) + { + // so the isSet() method will work + _alreadySet = true; + + // Matched argument: increment value. + ++_value; + + _checkWithVisitor(); + + return true; + } + else if ( combinedSwitchesMatch( args[*i] ) ) + { + // so the isSet() method will work + _alreadySet = true; + + // Matched argument: increment value. + ++_value; + + // Check for more in argument and increment value. + while ( combinedSwitchesMatch( args[*i] ) ) + ++_value; + + _checkWithVisitor(); + + return false; + } + else + return false; +} + +inline std::string +MultiSwitchArg::shortID(const std::string& val) const +{ + return Arg::shortID(val) + " ... "; +} + +inline std::string +MultiSwitchArg::longID(const std::string& val) const +{ + return Arg::longID(val) + " (accepted multiple times)"; +} + +inline void +MultiSwitchArg::reset() +{ + MultiSwitchArg::_value = MultiSwitchArg::_default; +} + +////////////////////////////////////////////////////////////////////// +//END MultiSwitchArg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/OptionalUnlabeledTracker.h b/components/mkspiffs/src/tclap/OptionalUnlabeledTracker.h new file mode 100644 index 0000000..8174c5f --- /dev/null +++ b/components/mkspiffs/src/tclap/OptionalUnlabeledTracker.h @@ -0,0 +1,62 @@ + + +/****************************************************************************** + * + * file: OptionalUnlabeledTracker.h + * + * Copyright (c) 2005, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_OPTIONAL_UNLABELED_TRACKER_H +#define TCLAP_OPTIONAL_UNLABELED_TRACKER_H + +#include + +namespace TCLAP { + +class OptionalUnlabeledTracker +{ + + public: + + static void check( bool req, const std::string& argName ); + + static void gotOptional() { alreadyOptionalRef() = true; } + + static bool& alreadyOptional() { return alreadyOptionalRef(); } + + private: + + static bool& alreadyOptionalRef() { static bool ct = false; return ct; } +}; + + +inline void OptionalUnlabeledTracker::check( bool req, const std::string& argName ) +{ + if ( OptionalUnlabeledTracker::alreadyOptional() ) + throw( SpecificationException( + "You can't specify ANY Unlabeled Arg following an optional Unlabeled Arg", + argName ) ); + + if ( !req ) + OptionalUnlabeledTracker::gotOptional(); +} + + +} // namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/StandardTraits.h b/components/mkspiffs/src/tclap/StandardTraits.h new file mode 100644 index 0000000..46d7f6f --- /dev/null +++ b/components/mkspiffs/src/tclap/StandardTraits.h @@ -0,0 +1,208 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: StandardTraits.h + * + * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +// This is an internal tclap file, you should probably not have to +// include this directly + +#ifndef TCLAP_STANDARD_TRAITS_H +#define TCLAP_STANDARD_TRAITS_H + +#ifdef HAVE_CONFIG_H +#include // To check for long long +#endif + +// If Microsoft has already typedef'd wchar_t as an unsigned +// short, then compiles will break because it's as if we're +// creating ArgTraits twice for unsigned short. Thus... +#ifdef _MSC_VER +#ifndef _NATIVE_WCHAR_T_DEFINED +#define TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS +#endif +#endif + +namespace TCLAP { + +// ====================================================================== +// Integer types +// ====================================================================== + +/** + * longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * ints have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * shorts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * chars have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +#ifdef HAVE_LONG_LONG +/** + * long longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +// ====================================================================== +// Unsigned integer types +// ====================================================================== + +/** + * unsigned longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned ints have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned shorts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned chars have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +// Microsoft implements size_t awkwardly. +#if defined(_MSC_VER) && defined(_M_X64) +/** + * size_ts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + + +#ifdef HAVE_LONG_LONG +/** + * unsigned long longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +// ====================================================================== +// Float types +// ====================================================================== + +/** + * floats have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * doubles have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +// ====================================================================== +// Other types +// ====================================================================== + +/** + * bools have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + + +/** + * wchar_ts have value-like semantics. + */ +#ifndef TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +/** + * Strings have string like argument traits. + */ +template<> +struct ArgTraits { + typedef StringLike ValueCategory; +}; + +template +void SetString(T &dst, const std::string &src) +{ + dst = src; +} + +} // namespace + +#endif + diff --git a/components/mkspiffs/src/tclap/StdOutput.h b/components/mkspiffs/src/tclap/StdOutput.h new file mode 100644 index 0000000..ec1cb93 --- /dev/null +++ b/components/mkspiffs/src/tclap/StdOutput.h @@ -0,0 +1,298 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: StdOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_STDCMDLINEOUTPUT_H +#define TCLAP_STDCMDLINEOUTPUT_H + +#include +#include +#include +#include +#include + +#include "CmdLineInterface.h" +#include "CmdLineOutput.h" +#include "XorHandler.h" +#include "Arg.h" + +namespace TCLAP { + +/** + * A class that isolates any output from the CmdLine object so that it + * may be easily modified. + */ +class StdOutput : public CmdLineOutput +{ + + public: + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + /** + * Writes a brief usage message with short args. + * \param c - The CmdLine object the output is generated for. + * \param os - The stream to write the message to. + */ + void _shortUsage( CmdLineInterface& c, std::ostream& os ) const; + + /** + * Writes a longer usage message with long and short args, + * provides descriptions and prints message. + * \param c - The CmdLine object the output is generated for. + * \param os - The stream to write the message to. + */ + void _longUsage( CmdLineInterface& c, std::ostream& os ) const; + + /** + * This function inserts line breaks and indents long strings + * according the params input. It will only break lines at spaces, + * commas and pipes. + * \param os - The stream to be printed to. + * \param s - The string to be printed. + * \param maxWidth - The maxWidth allowed for the output line. + * \param indentSpaces - The number of spaces to indent the first line. + * \param secondLineOffset - The number of spaces to indent the second + * and all subsequent lines in addition to indentSpaces. + */ + void spacePrint( std::ostream& os, + const std::string& s, + int maxWidth, + int indentSpaces, + int secondLineOffset ) const; + +}; + + +inline void StdOutput::version(CmdLineInterface& _cmd) +{ + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + + std::cout << std::endl << progName << " version: " + << xversion << std::endl << std::endl; +} + +inline void StdOutput::usage(CmdLineInterface& _cmd ) +{ + std::cout << std::endl << "USAGE: " << std::endl << std::endl; + + _shortUsage( _cmd, std::cout ); + + std::cout << std::endl << std::endl << "Where: " << std::endl << std::endl; + + _longUsage( _cmd, std::cout ); + + std::cout << std::endl; + +} + +inline void StdOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + std::string progName = _cmd.getProgramName(); + + std::cerr << "PARSE ERROR: " << e.argId() << std::endl + << " " << e.error() << std::endl << std::endl; + + if ( _cmd.hasHelpAndVersion() ) + { + std::cerr << "Brief USAGE: " << std::endl; + + _shortUsage( _cmd, std::cerr ); + + std::cerr << std::endl << "For complete USAGE and HELP type: " + << std::endl << " " << progName << " --help" + << std::endl << std::endl; + } + else + usage(_cmd); + + throw ExitException(1); +} + +inline void +StdOutput::_shortUsage( CmdLineInterface& _cmd, + std::ostream& os ) const +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + std::string s = progName + " "; + + // first the xor + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + s += " {"; + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); it++ ) + s += (*it)->shortID() + "|"; + + s[s.length()-1] = '}'; + } + + // then the rest + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + s += " " + (*it)->shortID(); + + // if the program name is too long, then adjust the second line offset + int secondLineOffset = static_cast(progName.length()) + 2; + if ( secondLineOffset > 75/2 ) + secondLineOffset = static_cast(75/2); + + spacePrint( os, s, 75, 3, secondLineOffset ); +} + +inline void +StdOutput::_longUsage( CmdLineInterface& _cmd, + std::ostream& os ) const +{ + std::list argList = _cmd.getArgList(); + std::string message = _cmd.getMessage(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + // first the xor + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); + it++ ) + { + spacePrint( os, (*it)->longID(), 75, 3, 3 ); + spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); + + if ( it+1 != xorList[i].end() ) + spacePrint(os, "-- OR --", 75, 9, 0); + } + os << std::endl << std::endl; + } + + // then the rest + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + { + spacePrint( os, (*it)->longID(), 75, 3, 3 ); + spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); + os << std::endl; + } + + os << std::endl; + + spacePrint( os, message, 75, 3, 0 ); +} + +inline void StdOutput::spacePrint( std::ostream& os, + const std::string& s, + int maxWidth, + int indentSpaces, + int secondLineOffset ) const +{ + int len = static_cast(s.length()); + + if ( (len + indentSpaces > maxWidth) && maxWidth > 0 ) + { + int allowedLen = maxWidth - indentSpaces; + int start = 0; + while ( start < len ) + { + // find the substring length + // int stringLen = std::min( len - start, allowedLen ); + // doing it this way to support a VisualC++ 2005 bug + using namespace std; + int stringLen = min( len - start, allowedLen ); + + // trim the length so it doesn't end in middle of a word + if ( stringLen == allowedLen ) + while ( stringLen >= 0 && + s[stringLen+start] != ' ' && + s[stringLen+start] != ',' && + s[stringLen+start] != '|' ) + stringLen--; + + // ok, the word is longer than the line, so just split + // wherever the line ends + if ( stringLen <= 0 ) + stringLen = allowedLen; + + // check for newlines + for ( int i = 0; i < stringLen; i++ ) + if ( s[start+i] == '\n' ) + stringLen = i+1; + + // print the indent + for ( int i = 0; i < indentSpaces; i++ ) + os << " "; + + if ( start == 0 ) + { + // handle second line offsets + indentSpaces += secondLineOffset; + + // adjust allowed len + allowedLen -= secondLineOffset; + } + + os << s.substr(start,stringLen) << std::endl; + + // so we don't start a line with a space + while ( s[stringLen+start] == ' ' && start < len ) + start++; + + start += stringLen; + } + } + else + { + for ( int i = 0; i < indentSpaces; i++ ) + os << " "; + os << s << std::endl; + } +} + +} //namespace TCLAP +#endif diff --git a/components/mkspiffs/src/tclap/SwitchArg.h b/components/mkspiffs/src/tclap/SwitchArg.h new file mode 100644 index 0000000..3a93a93 --- /dev/null +++ b/components/mkspiffs/src/tclap/SwitchArg.h @@ -0,0 +1,266 @@ + +/****************************************************************************** + * + * file: SwitchArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_SWITCH_ARG_H +#define TCLAP_SWITCH_ARG_H + +#include +#include + +#include "Arg.h" + +namespace TCLAP { + +/** + * A simple switch argument. If the switch is set on the command line, then + * the getValue method will return the opposite of the default value for the + * switch. + */ +class SwitchArg : public Arg +{ + protected: + + /** + * The value of the switch. + */ + bool _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + bool _default; + + public: + + /** + * SwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param def - The default value for this Switch. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool def = false, + Visitor* v = NULL); + + + /** + * SwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param parser - A CmdLine parser object to add this Arg to + * \param def - The default value for this Switch. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + bool def = false, + Visitor* v = NULL); + + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Checks a string to see if any of the chars in the string + * match the flag for this Switch. + */ + bool combinedSwitchesMatch(std::string& combined); + + /** + * Returns bool, whether or not the switch has been set. + */ + bool getValue(); + + virtual void reset(); + + private: + /** + * Checks to see if we've found the last match in + * a combined string. + */ + bool lastCombined(std::string& combined); + + /** + * Does the common processing of processArg. + */ + void commonProcessing(); +}; + +////////////////////////////////////////////////////////////////////// +//BEGIN SwitchArg.cpp +////////////////////////////////////////////////////////////////////// +inline SwitchArg::SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool default_val, + Visitor* v ) +: Arg(flag, name, desc, false, false, v), + _value( default_val ), + _default( default_val ) +{ } + +inline SwitchArg::SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + bool default_val, + Visitor* v ) +: Arg(flag, name, desc, false, false, v), + _value( default_val ), + _default(default_val) +{ + parser.add( this ); +} + +inline bool SwitchArg::getValue() { return _value; } + +inline bool SwitchArg::lastCombined(std::string& combinedSwitches ) +{ + for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) + if ( combinedSwitches[i] != Arg::blankChar() ) + return false; + + return true; +} + +inline bool SwitchArg::combinedSwitchesMatch(std::string& combinedSwitches ) +{ + // make sure this is actually a combined switch + if ( combinedSwitches.length() > 0 && + combinedSwitches[0] != Arg::flagStartString()[0] ) + return false; + + // make sure it isn't a long name + if ( combinedSwitches.substr( 0, Arg::nameStartString().length() ) == + Arg::nameStartString() ) + return false; + + // make sure the delimiter isn't in the string + if ( combinedSwitches.find_first_of( Arg::delimiter() ) != std::string::npos ) + return false; + + // ok, we're not specifying a ValueArg, so we know that we have + // a combined switch list. + for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) + if ( _flag.length() > 0 && + combinedSwitches[i] == _flag[0] && + _flag[0] != Arg::flagStartString()[0] ) + { + // update the combined switches so this one is no longer present + // this is necessary so that no unlabeled args are matched + // later in the processing. + //combinedSwitches.erase(i,1); + combinedSwitches[i] = Arg::blankChar(); + return true; + } + + // none of the switches passed in the list match. + return false; +} + +inline void SwitchArg::commonProcessing() +{ + if ( _xorSet ) + throw(CmdLineParseException( + "Mutually exclusive argument already set!", toString())); + + if ( _alreadySet ) + throw(CmdLineParseException("Argument already set!", toString())); + + _alreadySet = true; + + if ( _value == true ) + _value = false; + else + _value = true; + + _checkWithVisitor(); +} + +inline bool SwitchArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + // if the whole string matches the flag or name string + if ( argMatches( args[*i] ) ) + { + commonProcessing(); + + return true; + } + // if a substring matches the flag as part of a combination + else if ( combinedSwitchesMatch( args[*i] ) ) + { + // check again to ensure we don't misinterpret + // this as a MultiSwitchArg + if ( combinedSwitchesMatch( args[*i] ) ) + throw(CmdLineParseException("Argument already set!", + toString())); + + commonProcessing(); + + // We only want to return true if we've found the last combined + // match in the string, otherwise we return true so that other + // switches in the combination will have a chance to match. + return lastCombined( args[*i] ); + } + else + return false; +} + +inline void SwitchArg::reset() +{ + Arg::reset(); + _value = _default; +} +////////////////////////////////////////////////////////////////////// +//End SwitchArg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/UnlabeledMultiArg.h b/components/mkspiffs/src/tclap/UnlabeledMultiArg.h new file mode 100644 index 0000000..d38ce91 --- /dev/null +++ b/components/mkspiffs/src/tclap/UnlabeledMultiArg.h @@ -0,0 +1,301 @@ + +/****************************************************************************** + * + * file: UnlabeledMultiArg.h + * + * Copyright (c) 2003, Michael E. Smoot. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H +#define TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H + +#include +#include + +#include "MultiArg.h" +#include "OptionalUnlabeledTracker.h" + +namespace TCLAP { + +/** + * Just like a MultiArg, except that the arguments are unlabeled. Basically, + * this Arg will slurp up everything that hasn't been matched to another + * Arg. + */ +template +class UnlabeledMultiArg : public MultiArg +{ + + // If compiler has two stage name lookup (as gcc >= 3.4 does) + // this is requried to prevent undef. symbols + using MultiArg::_ignoreable; + using MultiArg::_hasBlanks; + using MultiArg::_extractValue; + using MultiArg::_typeDesc; + using MultiArg::_name; + using MultiArg::_description; + using MultiArg::_alreadySet; + using MultiArg::toString; + + public: + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + bool ignoreable = false, + Visitor* v = NULL ); + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns the a short id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Returns the a long id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Opertor ==. + * \param a - The Arg to be compared to this. + */ + virtual bool operator==(const Arg& a) const; + + /** + * Pushes this to back of list rather than front. + * \param argList - The list this should be added to. + */ + virtual void addToList( std::list& argList ) const; +}; + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); +} + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); + parser.add( this ); +} + + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); +} + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); + parser.add( this ); +} + + +template +bool UnlabeledMultiArg::processArg(int *i, std::vector& args) +{ + + if ( _hasBlanks( args[*i] ) ) + return false; + + // never ignore an unlabeled multi arg + + + // always take the first value, regardless of the start string + _extractValue( args[(*i)] ); + + /* + // continue taking args until we hit the end or a start string + while ( (unsigned int)(*i)+1 < args.size() && + args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && + args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) + _extractValue( args[++(*i)] ); + */ + + _alreadySet = true; + + return true; +} + +template +std::string UnlabeledMultiArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + "> ..."; +} + +template +std::string UnlabeledMultiArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + "> (accepted multiple times)"; +} + +template +bool UnlabeledMultiArg::operator==(const Arg& a) const +{ + if ( _name == a.getName() || _description == a.getDescription() ) + return true; + else + return false; +} + +template +void UnlabeledMultiArg::addToList( std::list& argList ) const +{ + argList.push_back( const_cast(static_cast(this)) ); +} + +} + +#endif diff --git a/components/mkspiffs/src/tclap/UnlabeledValueArg.h b/components/mkspiffs/src/tclap/UnlabeledValueArg.h new file mode 100644 index 0000000..8debba7 --- /dev/null +++ b/components/mkspiffs/src/tclap/UnlabeledValueArg.h @@ -0,0 +1,340 @@ + +/****************************************************************************** + * + * file: UnlabeledValueArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_UNLABELED_VALUE_ARGUMENT_H +#define TCLAP_UNLABELED_VALUE_ARGUMENT_H + +#include +#include + +#include "ValueArg.h" +#include "OptionalUnlabeledTracker.h" + + +namespace TCLAP { + +/** + * The basic unlabeled argument that parses a value. + * This is a template class, which means the type T defines the type + * that a given object will attempt to parse when an UnlabeledValueArg + * is reached in the list of args that the CmdLine iterates over. + */ +template +class UnlabeledValueArg : public ValueArg +{ + + // If compiler has two stage name lookup (as gcc >= 3.4 does) + // this is requried to prevent undef. symbols + using ValueArg::_ignoreable; + using ValueArg::_hasBlanks; + using ValueArg::_extractValue; + using ValueArg::_typeDesc; + using ValueArg::_name; + using ValueArg::_description; + using ValueArg::_alreadySet; + using ValueArg::toString; + + public: + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + bool ignoreable = false, + Visitor* v = NULL); + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + bool ignoreable = false, + Visitor* v = NULL ); + + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. Handling specific to + * unlabled arguments. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Overrides shortID for specific behavior. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Overrides longID for specific behavior. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Overrides operator== for specific behavior. + */ + virtual bool operator==(const Arg& a ) const; + + /** + * Instead of pushing to the front of list, push to the back. + * \param argList - The list to add this to. + */ + virtual void addToList( std::list& argList ) const; + +}; + +/** + * Constructor implemenation. + */ +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, typeDesc, v) +{ + _ignoreable = ignoreable; + + OptionalUnlabeledTracker::check(req, toString()); + +} + +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); + parser.add( this ); +} + +/** + * Constructor implemenation. + */ +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); +} + +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); + parser.add( this ); +} + +/** + * Implementation of processArg(). + */ +template +bool UnlabeledValueArg::processArg(int *i, std::vector& args) +{ + + if ( _alreadySet ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + // never ignore an unlabeled arg + + _extractValue( args[*i] ); + _alreadySet = true; + return true; +} + +/** + * Overriding shortID for specific output. + */ +template +std::string UnlabeledValueArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + ">"; +} + +/** + * Overriding longID for specific output. + */ +template +std::string UnlabeledValueArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + + // Ideally we would like to be able to use RTTI to return the name + // of the type required for this argument. However, g++ at least, + // doesn't appear to return terribly useful "names" of the types. + return std::string("<") + _typeDesc + ">"; +} + +/** + * Overriding operator== for specific behavior. + */ +template +bool UnlabeledValueArg::operator==(const Arg& a ) const +{ + if ( _name == a.getName() || _description == a.getDescription() ) + return true; + else + return false; +} + +template +void UnlabeledValueArg::addToList( std::list& argList ) const +{ + argList.push_back( const_cast(static_cast(this)) ); +} + +} +#endif diff --git a/components/mkspiffs/src/tclap/ValueArg.h b/components/mkspiffs/src/tclap/ValueArg.h new file mode 100644 index 0000000..95f9bb7 --- /dev/null +++ b/components/mkspiffs/src/tclap/ValueArg.h @@ -0,0 +1,425 @@ +/****************************************************************************** + * + * file: ValueArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_VALUE_ARGUMENT_H +#define TCLAP_VALUE_ARGUMENT_H + +#include +#include + +#include "Arg.h" +#include "Constraint.h" + +namespace TCLAP { + +/** + * The basic labeled argument that parses a value. + * This is a template class, which means the type T defines the type + * that a given object will attempt to parse when the flag/name is matched + * on the command line. While there is nothing stopping you from creating + * an unflagged ValueArg, it is unwise and would cause significant problems. + * Instead use an UnlabeledValueArg. + */ +template +class ValueArg : public Arg +{ + protected: + + /** + * The value parsed from the command line. + * Can be of any type, as long as the >> operator for the type + * is defined. + */ + T _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + T _default; + + /** + * A human readable description of the type to be parsed. + * This is a hack, plain and simple. Ideally we would use RTTI to + * return the name of type T, but until there is some sort of + * consistent support for human readable names, we are left to our + * own devices. + */ + std::string _typeDesc; + + /** + * A Constraint this Arg must conform to. + */ + Constraint* _constraint; + + /** + * Extracts the value from the string. + * Attempts to parse string as type T, if this fails an exception + * is thrown. + * \param val - value to be parsed. + */ + void _extractValue( const std::string& val ); + + public: + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + Visitor* v = NULL); + + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns the value of the argument. + */ + T& getValue() ; + + /** + * Specialization of shortID. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val = "val") const; + + /** + * Specialization of longID. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val = "val") const; + + virtual void reset() ; + +private: + /** + * Prevent accidental copying + */ + ValueArg(const ValueArg& rhs); + ValueArg& operator=(const ValueArg& rhs); +}; + + +/** + * Constructor implementation. + */ +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( typeDesc ), + _constraint( NULL ) +{ } + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( typeDesc ), + _constraint( NULL ) +{ + parser.add( this ); +} + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( constraint->shortID() ), + _constraint( constraint ) +{ } + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( constraint->shortID() ), + _constraint( constraint ) +{ + parser.add( this ); +} + + +/** + * Implementation of getValue(). + */ +template +T& ValueArg::getValue() { return _value; } + +/** + * Implementation of processArg(). + */ +template +bool ValueArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + std::string flag = args[*i]; + + std::string value = ""; + trimFlag( flag, value ); + + if ( argMatches( flag ) ) + { + if ( _alreadySet ) + { + if ( _xorSet ) + throw( CmdLineParseException( + "Mutually exclusive argument already set!", + toString()) ); + else + throw( CmdLineParseException("Argument already set!", + toString()) ); + } + + if ( Arg::delimiter() != ' ' && value == "" ) + throw( ArgParseException( + "Couldn't find delimiter for this argument!", + toString() ) ); + + if ( value == "" ) + { + (*i)++; + if ( static_cast(*i) < args.size() ) + _extractValue( args[*i] ); + else + throw( ArgParseException("Missing a value for this argument!", + toString() ) ); + } + else + _extractValue( value ); + + _alreadySet = true; + _checkWithVisitor(); + return true; + } + else + return false; +} + +/** + * Implementation of shortID. + */ +template +std::string ValueArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::shortID( _typeDesc ); +} + +/** + * Implementation of longID. + */ +template +std::string ValueArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::longID( _typeDesc ); +} + +template +void ValueArg::_extractValue( const std::string& val ) +{ + try { + ExtractValue(_value, val, typename ArgTraits::ValueCategory()); + } catch( ArgParseException &e) { + throw ArgParseException(e.error(), toString()); + } + + if ( _constraint != NULL ) + if ( ! _constraint->check( _value ) ) + throw( CmdLineParseException( "Value '" + val + + + "' does not meet constraint: " + + _constraint->description(), + toString() ) ); +} + +template +void ValueArg::reset() +{ + Arg::reset(); + _value = _default; +} + +} // namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/ValuesConstraint.h b/components/mkspiffs/src/tclap/ValuesConstraint.h new file mode 100644 index 0000000..27d3a6d --- /dev/null +++ b/components/mkspiffs/src/tclap/ValuesConstraint.h @@ -0,0 +1,148 @@ + + +/****************************************************************************** + * + * file: ValuesConstraint.h + * + * Copyright (c) 2005, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_VALUESCONSTRAINT_H +#define TCLAP_VALUESCONSTRAINT_H + +#include +#include +#include "Constraint.h" + +#ifdef HAVE_CONFIG_H +#include +#else +#define HAVE_SSTREAM +#endif + +#if defined(HAVE_SSTREAM) +#include +#elif defined(HAVE_STRSTREAM) +#include +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + +namespace TCLAP { + +/** + * A Constraint that constrains the Arg to only those values specified + * in the constraint. + */ +template +class ValuesConstraint : public Constraint +{ + + public: + + /** + * Constructor. + * \param allowed - vector of allowed values. + */ + ValuesConstraint(std::vector& allowed); + + /** + * Virtual destructor. + */ + virtual ~ValuesConstraint() {} + + /** + * Returns a description of the Constraint. + */ + virtual std::string description() const; + + /** + * Returns the short ID for the Constraint. + */ + virtual std::string shortID() const; + + /** + * The method used to verify that the value parsed from the command + * line meets the constraint. + * \param value - The value that will be checked. + */ + virtual bool check(const T& value) const; + + protected: + + /** + * The list of valid values. + */ + std::vector _allowed; + + /** + * The string used to describe the allowed values of this constraint. + */ + std::string _typeDesc; + +}; + +template +ValuesConstraint::ValuesConstraint(std::vector& allowed) +: _allowed(allowed), + _typeDesc("") +{ + for ( unsigned int i = 0; i < _allowed.size(); i++ ) + { + +#if defined(HAVE_SSTREAM) + std::ostringstream os; +#elif defined(HAVE_STRSTREAM) + std::ostrstream os; +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + + os << _allowed[i]; + + std::string temp( os.str() ); + + if ( i > 0 ) + _typeDesc += "|"; + _typeDesc += temp; + } +} + +template +bool ValuesConstraint::check( const T& val ) const +{ + if ( std::find(_allowed.begin(),_allowed.end(),val) == _allowed.end() ) + return false; + else + return true; +} + +template +std::string ValuesConstraint::shortID() const +{ + return _typeDesc; +} + +template +std::string ValuesConstraint::description() const +{ + return _typeDesc; +} + + +} //namespace TCLAP +#endif + diff --git a/components/mkspiffs/src/tclap/VersionVisitor.h b/components/mkspiffs/src/tclap/VersionVisitor.h new file mode 100644 index 0000000..d7e67d8 --- /dev/null +++ b/components/mkspiffs/src/tclap/VersionVisitor.h @@ -0,0 +1,81 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: VersionVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_VERSION_VISITOR_H +#define TCLAP_VERSION_VISITOR_H + +#include "CmdLineInterface.h" +#include "CmdLineOutput.h" +#include "Visitor.h" + +namespace TCLAP { + +/** + * A Vistor that will call the version method of the given CmdLineOutput + * for the specified CmdLine object and then exit. + */ +class VersionVisitor: public Visitor +{ + private: + /** + * Prevent accidental copying + */ + VersionVisitor(const VersionVisitor& rhs); + VersionVisitor& operator=(const VersionVisitor& rhs); + + protected: + + /** + * The CmdLine of interest. + */ + CmdLineInterface* _cmd; + + /** + * The output object. + */ + CmdLineOutput** _out; + + public: + + /** + * Constructor. + * \param cmd - The CmdLine the output is generated for. + * \param out - The type of output. + */ + VersionVisitor( CmdLineInterface* cmd, CmdLineOutput** out ) + : Visitor(), _cmd( cmd ), _out( out ) { } + + /** + * Calls the version method of the output object using the + * specified CmdLine. + */ + void visit() { + (*_out)->version(*_cmd); + throw ExitException(0); + } + +}; + +} + +#endif diff --git a/components/mkspiffs/src/tclap/Visitor.h b/components/mkspiffs/src/tclap/Visitor.h new file mode 100644 index 0000000..38ddcbd --- /dev/null +++ b/components/mkspiffs/src/tclap/Visitor.h @@ -0,0 +1,53 @@ + +/****************************************************************************** + * + * file: Visitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + + +#ifndef TCLAP_VISITOR_H +#define TCLAP_VISITOR_H + +namespace TCLAP { + +/** + * A base class that defines the interface for visitors. + */ +class Visitor +{ + public: + + /** + * Constructor. Does nothing. + */ + Visitor() { } + + /** + * Destructor. Does nothing. + */ + virtual ~Visitor() { } + + /** + * Does nothing. Should be overridden by child. + */ + virtual void visit() { } +}; + +} + +#endif diff --git a/components/mkspiffs/src/tclap/XorHandler.h b/components/mkspiffs/src/tclap/XorHandler.h new file mode 100644 index 0000000..13551fb --- /dev/null +++ b/components/mkspiffs/src/tclap/XorHandler.h @@ -0,0 +1,166 @@ + +/****************************************************************************** + * + * file: XorHandler.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_XORHANDLER_H +#define TCLAP_XORHANDLER_H + +#include "Arg.h" +#include +#include +#include +#include + +namespace TCLAP { + +/** + * This class handles lists of Arg's that are to be XOR'd on the command + * line. This is used by CmdLine and you shouldn't ever use it. + */ +class XorHandler +{ + protected: + + /** + * The list of of lists of Arg's to be or'd together. + */ + std::vector< std::vector > _orList; + + public: + + /** + * Constructor. Does nothing. + */ + XorHandler( ) : _orList(std::vector< std::vector >()) {} + + /** + * Add a list of Arg*'s that will be orred together. + * \param ors - list of Arg* that will be xor'd. + */ + void add( std::vector& ors ); + + /** + * Checks whether the specified Arg is in one of the xor lists and + * if it does match one, returns the size of the xor list that the + * Arg matched. If the Arg matches, then it also sets the rest of + * the Arg's in the list. You shouldn't use this. + * \param a - The Arg to be checked. + */ + int check( const Arg* a ); + + /** + * Returns the XOR specific short usage. + */ + std::string shortUsage(); + + /** + * Prints the XOR specific long usage. + * \param os - Stream to print to. + */ + void printLongUsage(std::ostream& os); + + /** + * Simply checks whether the Arg is contained in one of the arg + * lists. + * \param a - The Arg to be checked. + */ + bool contains( const Arg* a ); + + std::vector< std::vector >& getXorList(); + +}; + + +////////////////////////////////////////////////////////////////////// +//BEGIN XOR.cpp +////////////////////////////////////////////////////////////////////// +inline void XorHandler::add( std::vector& ors ) +{ + _orList.push_back( ors ); +} + +inline int XorHandler::check( const Arg* a ) +{ + // iterate over each XOR list + for ( int i = 0; static_cast(i) < _orList.size(); i++ ) + { + // if the XOR list contains the arg.. + ArgVectorIterator ait = std::find( _orList[i].begin(), + _orList[i].end(), a ); + if ( ait != _orList[i].end() ) + { + // first check to see if a mutually exclusive switch + // has not already been set + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a != (*it) && (*it)->isSet() ) + throw(CmdLineParseException( + "Mutually exclusive argument already set!", + (*it)->toString())); + + // go through and set each arg that is not a + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a != (*it) ) + (*it)->xorSet(); + + // return the number of required args that have now been set + if ( (*ait)->allowMore() ) + return 0; + else + return static_cast(_orList[i].size()); + } + } + + if ( a->isRequired() ) + return 1; + else + return 0; +} + +inline bool XorHandler::contains( const Arg* a ) +{ + for ( int i = 0; static_cast(i) < _orList.size(); i++ ) + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a == (*it) ) + return true; + + return false; +} + +inline std::vector< std::vector >& XorHandler::getXorList() +{ + return _orList; +} + + + +////////////////////////////////////////////////////////////////////// +//END XOR.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/components/mkspiffs/src/tclap/ZshCompletionOutput.h b/components/mkspiffs/src/tclap/ZshCompletionOutput.h new file mode 100644 index 0000000..69dc099 --- /dev/null +++ b/components/mkspiffs/src/tclap/ZshCompletionOutput.h @@ -0,0 +1,323 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ZshCompletionOutput.h + * + * Copyright (c) 2006, Oliver Kiddle + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * 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. + * + *****************************************************************************/ + +#ifndef TCLAP_ZSHCOMPLETIONOUTPUT_H +#define TCLAP_ZSHCOMPLETIONOUTPUT_H + +#include +#include +#include +#include +#include + +#include "CmdLineInterface.h" +#include "CmdLineOutput.h" +#include "XorHandler.h" +#include "Arg.h" + +namespace TCLAP { + +/** + * A class that generates a Zsh completion function as output from the usage() + * method for the given CmdLine and its Args. + */ +class ZshCompletionOutput : public CmdLineOutput +{ + + public: + + ZshCompletionOutput(); + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + void basename( std::string& s ); + void quoteSpecialChars( std::string& s ); + + std::string getMutexList( CmdLineInterface& _cmd, Arg* a ); + void printOption( Arg* it, std::string mutex ); + void printArg( Arg* it ); + + std::map common; + char theDelimiter; +}; + +ZshCompletionOutput::ZshCompletionOutput() +: common(std::map()), + theDelimiter('=') +{ + common["host"] = "_hosts"; + common["hostname"] = "_hosts"; + common["file"] = "_files"; + common["filename"] = "_files"; + common["user"] = "_users"; + common["username"] = "_users"; + common["directory"] = "_directories"; + common["path"] = "_directories"; + common["url"] = "_urls"; +} + +inline void ZshCompletionOutput::version(CmdLineInterface& _cmd) +{ + std::cout << _cmd.getVersion() << std::endl; +} + +inline void ZshCompletionOutput::usage(CmdLineInterface& _cmd ) +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + theDelimiter = _cmd.getDelimiter(); + basename(progName); + + std::cout << "#compdef " << progName << std::endl << std::endl << + "# " << progName << " version " << _cmd.getVersion() << std::endl << std::endl << + "_arguments -s -S"; + + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + { + if ( (*it)->shortID().at(0) == '<' ) + printArg((*it)); + else if ( (*it)->getFlag() != "-" ) + printOption((*it), getMutexList(_cmd, *it)); + } + + std::cout << std::endl; +} + +inline void ZshCompletionOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + static_cast(_cmd); // unused + std::cout << e.what() << std::endl; +} + +inline void ZshCompletionOutput::quoteSpecialChars( std::string& s ) +{ + size_t idx = s.find_last_of(':'); + while ( idx != std::string::npos ) + { + s.insert(idx, 1, '\\'); + idx = s.find_last_of(':', idx); + } + idx = s.find_last_of('\''); + while ( idx != std::string::npos ) + { + s.insert(idx, "'\\'"); + if (idx == 0) + idx = std::string::npos; + else + idx = s.find_last_of('\'', --idx); + } +} + +inline void ZshCompletionOutput::basename( std::string& s ) +{ + size_t p = s.find_last_of('/'); + if ( p != std::string::npos ) + { + s.erase(0, p + 1); + } +} + +inline void ZshCompletionOutput::printArg(Arg* a) +{ + static int count = 1; + + std::cout << " \\" << std::endl << " '"; + if ( a->acceptsMultipleValues() ) + std::cout << '*'; + else + std::cout << count++; + std::cout << ':'; + if ( !a->isRequired() ) + std::cout << ':'; + + std::cout << a->getName() << ':'; + std::map::iterator compArg = common.find(a->getName()); + if ( compArg != common.end() ) + { + std::cout << compArg->second; + } + else + { + std::cout << "_guard \"^-*\" " << a->getName(); + } + std::cout << '\''; +} + +inline void ZshCompletionOutput::printOption(Arg* a, std::string mutex) +{ + std::string flag = a->flagStartChar() + a->getFlag(); + std::string name = a->nameStartString() + a->getName(); + std::string desc = a->getDescription(); + + // remove full stop and capitalisation from description as + // this is the convention for zsh function + if (!desc.compare(0, 12, "(required) ")) + { + desc.erase(0, 12); + } + if (!desc.compare(0, 15, "(OR required) ")) + { + desc.erase(0, 15); + } + size_t len = desc.length(); + if (len && desc.at(--len) == '.') + { + desc.erase(len); + } + if (len) + { + desc.replace(0, 1, 1, tolower(desc.at(0))); + } + + std::cout << " \\" << std::endl << " '" << mutex; + + if ( a->getFlag().empty() ) + { + std::cout << name; + } + else + { + std::cout << "'{" << flag << ',' << name << "}'"; + } + if ( theDelimiter == '=' && a->isValueRequired() ) + std::cout << "=-"; + quoteSpecialChars(desc); + std::cout << '[' << desc << ']'; + + if ( a->isValueRequired() ) + { + std::string arg = a->shortID(); + arg.erase(0, arg.find_last_of(theDelimiter) + 1); + if ( arg.at(arg.length()-1) == ']' ) + arg.erase(arg.length()-1); + if ( arg.at(arg.length()-1) == ']' ) + { + arg.erase(arg.length()-1); + } + if ( arg.at(0) == '<' ) + { + arg.erase(arg.length()-1); + arg.erase(0, 1); + } + size_t p = arg.find('|'); + if ( p != std::string::npos ) + { + do + { + arg.replace(p, 1, 1, ' '); + } + while ( (p = arg.find_first_of('|', p)) != std::string::npos ); + quoteSpecialChars(arg); + std::cout << ": :(" << arg << ')'; + } + else + { + std::cout << ':' << arg; + std::map::iterator compArg = common.find(arg); + if ( compArg != common.end() ) + { + std::cout << ':' << compArg->second; + } + } + } + + std::cout << '\''; +} + +inline std::string ZshCompletionOutput::getMutexList( CmdLineInterface& _cmd, Arg* a) +{ + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + if (a->getName() == "help" || a->getName() == "version") + { + return "(-)"; + } + + std::ostringstream list; + if ( a->acceptsMultipleValues() ) + { + list << '*'; + } + + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); + it++) + if ( a == (*it) ) + { + list << '('; + for ( ArgVectorIterator iu = xorList[i].begin(); + iu != xorList[i].end(); + iu++ ) + { + bool notCur = (*iu) != a; + bool hasFlag = !(*iu)->getFlag().empty(); + if ( iu != xorList[i].begin() && (notCur || hasFlag) ) + list << ' '; + if (hasFlag) + list << (*iu)->flagStartChar() << (*iu)->getFlag() << ' '; + if ( notCur || hasFlag ) + list << (*iu)->nameStartString() << (*iu)->getName(); + } + list << ')'; + return list.str(); + } + } + + // wasn't found in xor list + if (!a->getFlag().empty()) { + list << "(" << a->flagStartChar() << a->getFlag() << ' ' << + a->nameStartString() << a->getName() << ')'; + } + + return list.str(); +} + +} //namespace TCLAP +#endif diff --git a/components/spidriver/component.mk b/components/spidriver/component.mk new file mode 100644 index 0000000..0f76ee5 --- /dev/null +++ b/components/spidriver/component.mk @@ -0,0 +1,7 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_SRCDIRS := . +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/components/spidriver/spi_master_lobo.c b/components/spidriver/spi_master_lobo.c new file mode 100644 index 0000000..5fb267a --- /dev/null +++ b/components/spidriver/spi_master_lobo.c @@ -0,0 +1,1153 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* +---------------------------------------- +Non DMA version of the spi_master driver +---------------------------------------- +------------------------------------------------------------------------------------ +Based on esp-idf 'spi_master', modified by LoBo (https://github.com/loboris) 03/2017 +------------------------------------------------------------------------------------ + +* Transfers data to SPI device in direct mode, not using DMA +* All configuration options (bus, device, transaction) are the same as in spi_master driver +* Transfers uses the semaphore (taken in select function & given in deselect function) to protect the transfer +* Number of the devices attached to the bus which uses hardware CS can be 3 ('NO_CS') +* Additional devices which uses software CS can be attached to the bus, up to 'NO_DEV' +* 'spi_bus_initialize' & 'spi_bus_remove' functions are removed, spi bus is initiated/removed in spi_lobo_bus_add_device/spi_lobo_bus_remove_device when needed +* 'spi_lobo_bus_add_device' function has added parameter 'bus_config' and automatically initializes spi bus device if not already initialized +* 'spi_lobo_bus_remove_device' automatically removes spi bus device if no other devices are attached to it. +* Devices can have individual bus_configs, so different mosi, miso, sck pins can be configured for each device + Reconfiguring the bus is done automaticaly in 'spi_lobo_device_select' function +* 'spi_lobo_device_select' & 'spi_lobo_device_deselect' functions handles devices configuration changes and software CS +* Some helper functions are added ('spi_lobo_get_speed', 'spi_lobo_set_speed', ...) +* All structures are available in header file for easy creation of user low level spi functions. See **tftfunc.c** source for examples. +* Transimt and receive lenghts are limited only by available memory + + +Main driver's function is 'spi_lobo_transfer_data()' + + * TRANSMIT 8-bit data to spi device from 'trans->tx_buffer' or 'trans->tx_data' (trans->lenght/8 bytes) + * and RECEIVE data to 'trans->rx_buffer' or 'trans->rx_data' (trans->rx_length/8 bytes) + * Lengths must be 8-bit multiples! + * If trans->rx_buffer is NULL or trans->rx_length is 0, only transmits data + * If trans->tx_buffer is NULL or trans->length is 0, only receives data + * If the device is in duplex mode (SPI_DEVICE_HALFDUPLEX flag NOT set), data are transmitted and received simultaneously. + * If the device is in half duplex mode (SPI_DEVICE_HALFDUPLEX flag IS set), data are received after transmission + * 'address', 'command' and 'dummy bits' are transmitted before data phase IF set in device's configuration + * and IF 'trans->length' and 'trans->rx_length' are NOT both 0 + * If configured, devices 'pre_cb' callback is called before and 'post_cb' after the transmission + * If device was not previously selected, it will be selected before transmission and deselected after transmission. + +*/ + +/* + Replace this include with + #include "driver/spi_master_lobo.h" + if the driver is located in esp-isf/components +*/ +#include "freertos/FreeRTOS.h" +#include +#include +#include "soc/gpio_sig_map.h" +#include "soc/spi_reg.h" +#include "soc/dport_reg.h" +#include "soc/rtc_cntl_reg.h" +#include "rom/ets_sys.h" +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_err.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "soc/soc.h" +#include "soc/dport_reg.h" +#include "soc/uart_struct.h" +#include "driver/uart.h" +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" +#include "esp_heap_caps.h" +#include "driver/periph_ctrl.h" +#include "spi_master_lobo.h" + + +static spi_lobo_host_t *spihost[3] = {NULL}; + + +static const char *SPI_TAG = "spi_lobo_master"; +#define SPI_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +/* + Stores a bunch of per-spi-peripheral data. +*/ +typedef struct { + const uint8_t spiclk_out; //GPIO mux output signals + const uint8_t spid_out; + const uint8_t spiq_out; + const uint8_t spiwp_out; + const uint8_t spihd_out; + const uint8_t spid_in; //GPIO mux input signals + const uint8_t spiq_in; + const uint8_t spiwp_in; + const uint8_t spihd_in; + const uint8_t spics_out[3]; // /CS GPIO output mux signals + const uint8_t spiclk_native; //IO pins of IO_MUX muxed signals + const uint8_t spid_native; + const uint8_t spiq_native; + const uint8_t spiwp_native; + const uint8_t spihd_native; + const uint8_t spics0_native; + const uint8_t irq; //irq source for interrupt mux + const uint8_t irq_dma; //dma irq source for interrupt mux + const periph_module_t module; //peripheral module, for enabling clock etc + spi_dev_t *hw; //Pointer to the hardware registers +} spi_signal_conn_t; + +/* + Bunch of constants for every SPI peripheral: GPIO signals, irqs, hw addr of registers etc +*/ +static const spi_signal_conn_t io_signal[3]={ + { + .spiclk_out=SPICLK_OUT_IDX, + .spid_out=SPID_OUT_IDX, + .spiq_out=SPIQ_OUT_IDX, + .spiwp_out=SPIWP_OUT_IDX, + .spihd_out=SPIHD_OUT_IDX, + .spid_in=SPID_IN_IDX, + .spiq_in=SPIQ_IN_IDX, + .spiwp_in=SPIWP_IN_IDX, + .spihd_in=SPIHD_IN_IDX, + .spics_out={SPICS0_OUT_IDX, SPICS1_OUT_IDX, SPICS2_OUT_IDX}, + .spiclk_native=6, + .spid_native=8, + .spiq_native=7, + .spiwp_native=10, + .spihd_native=9, + .spics0_native=11, + .irq=ETS_SPI1_INTR_SOURCE, + .irq_dma=ETS_SPI1_DMA_INTR_SOURCE, + .module=PERIPH_SPI_MODULE, + .hw=&SPI1 + }, { + .spiclk_out=HSPICLK_OUT_IDX, + .spid_out=HSPID_OUT_IDX, + .spiq_out=HSPIQ_OUT_IDX, + .spiwp_out=HSPIWP_OUT_IDX, + .spihd_out=HSPIHD_OUT_IDX, + .spid_in=HSPID_IN_IDX, + .spiq_in=HSPIQ_IN_IDX, + .spiwp_in=HSPIWP_IN_IDX, + .spihd_in=HSPIHD_IN_IDX, + .spics_out={HSPICS0_OUT_IDX, HSPICS1_OUT_IDX, HSPICS2_OUT_IDX}, + .spiclk_native=14, + .spid_native=13, + .spiq_native=12, + .spiwp_native=2, + .spihd_native=4, + .spics0_native=15, + .irq=ETS_SPI2_INTR_SOURCE, + .irq_dma=ETS_SPI2_DMA_INTR_SOURCE, + .module=PERIPH_HSPI_MODULE, + .hw=&SPI2 + }, { + .spiclk_out=VSPICLK_OUT_IDX, + .spid_out=VSPID_OUT_IDX, + .spiq_out=VSPIQ_OUT_IDX, + .spiwp_out=VSPIWP_OUT_IDX, + .spihd_out=VSPIHD_OUT_IDX, + .spid_in=VSPID_IN_IDX, + .spiq_in=VSPIQ_IN_IDX, + .spiwp_in=VSPIWP_IN_IDX, + .spihd_in=VSPIHD_IN_IDX, + .spics_out={VSPICS0_OUT_IDX, VSPICS1_OUT_IDX, VSPICS2_OUT_IDX}, + .spiclk_native=18, + .spid_native=23, + .spiq_native=19, + .spiwp_native=22, + .spihd_native=21, + .spics0_native=5, + .irq=ETS_SPI3_INTR_SOURCE, + .irq_dma=ETS_SPI3_DMA_INTR_SOURCE, + .module=PERIPH_VSPI_MODULE, + .hw=&SPI3 + } +}; + + +//====================================================================================================== + +#define DMA_CHANNEL_ENABLED(dma_chan) (BIT(dma_chan-1)) + +typedef void(*dmaworkaround_cb_t)(void *arg); + +//Set up a list of dma descriptors. dmadesc is an array of descriptors. Data is the buffer to point to. +//-------------------------------------------------------------------------------------------- +void spi_lobo_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx) +{ + int n = 0; + while (len) { + int dmachunklen = len; + if (dmachunklen > SPI_MAX_DMA_LEN) dmachunklen = SPI_MAX_DMA_LEN; + if (isrx) { + //Receive needs DMA length rounded to next 32-bit boundary + dmadesc[n].size = (dmachunklen + 3) & (~3); + dmadesc[n].length = (dmachunklen + 3) & (~3); + } else { + dmadesc[n].size = dmachunklen; + dmadesc[n].length = dmachunklen; + } + dmadesc[n].buf = (uint8_t *)data; + dmadesc[n].eof = 0; + dmadesc[n].sosf = 0; + dmadesc[n].owner = 1; + dmadesc[n].qe.stqe_next = &dmadesc[n + 1]; + len -= dmachunklen; + data += dmachunklen; + n++; + } + dmadesc[n - 1].eof = 1; //Mark last DMA desc as end of stream. + dmadesc[n - 1].qe.stqe_next = NULL; +} + + +/* +Code for workaround for DMA issue in ESP32 v0/v1 silicon +*/ + + +static volatile int dmaworkaround_channels_busy[2] = {0, 0}; +static dmaworkaround_cb_t dmaworkaround_cb; +static void *dmaworkaround_cb_arg; +static portMUX_TYPE dmaworkaround_mux = portMUX_INITIALIZER_UNLOCKED; +static int dmaworkaround_waiting_for_chan = 0; +static bool spi_periph_claimed[3] = {true, false, false}; +static uint8_t spi_dma_chan_enabled = 0; +static portMUX_TYPE spi_dma_spinlock = portMUX_INITIALIZER_UNLOCKED; + +//-------------------------------------------------------------------------------------------- +bool IRAM_ATTR spi_lobo_dmaworkaround_req_reset(int dmachan, dmaworkaround_cb_t cb, void *arg) +{ + int otherchan = (dmachan == 1) ? 2 : 1; + bool ret; + portENTER_CRITICAL(&dmaworkaround_mux); + if (dmaworkaround_channels_busy[otherchan-1]) { + //Other channel is busy. Call back when it's done. + dmaworkaround_cb = cb; + dmaworkaround_cb_arg = arg; + dmaworkaround_waiting_for_chan = otherchan; + ret = false; + } else { + //Reset DMA + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); + ret = true; + } + portEXIT_CRITICAL(&dmaworkaround_mux); + return ret; +} + +//------------------------------------------------------- +bool IRAM_ATTR spi_lobo_dmaworkaround_reset_in_progress() +{ + return (dmaworkaround_waiting_for_chan != 0); +} + +//----------------------------------------------------- +void IRAM_ATTR spi_lobo_dmaworkaround_idle(int dmachan) +{ + portENTER_CRITICAL(&dmaworkaround_mux); + dmaworkaround_channels_busy[dmachan-1] = 0; + if (dmaworkaround_waiting_for_chan == dmachan) { + //Reset DMA + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_DMA_RST); + dmaworkaround_waiting_for_chan = 0; + //Call callback + dmaworkaround_cb(dmaworkaround_cb_arg); + + } + portEXIT_CRITICAL(&dmaworkaround_mux); +} + +//---------------------------------------------------------------- +void IRAM_ATTR spi_lobo_dmaworkaround_transfer_active(int dmachan) +{ + portENTER_CRITICAL(&dmaworkaround_mux); + dmaworkaround_channels_busy[dmachan-1] = 1; + portEXIT_CRITICAL(&dmaworkaround_mux); +} + +//Returns true if this peripheral is successfully claimed, false if otherwise. +//----------------------------------------------------- +bool spi_lobo_periph_claim(spi_lobo_host_device_t host) +{ + bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], false, true); + if (ret) periph_module_enable(io_signal[host].module); + return ret; +} + +//Returns true if this peripheral is successfully freed, false if otherwise. +//----------------------------------------------- +bool spi_lobo_periph_free(spi_lobo_host_device_t host) +{ + bool ret = __sync_bool_compare_and_swap(&spi_periph_claimed[host], true, false); + if (ret) periph_module_disable(io_signal[host].module); + return ret; +} + +//----------------------------------------- +bool spi_lobo_dma_chan_claim (int dma_chan) +{ + bool ret = false; + assert( dma_chan == 1 || dma_chan == 2 ); + + portENTER_CRITICAL(&spi_dma_spinlock); + if ( !(spi_dma_chan_enabled & DMA_CHANNEL_ENABLED(dma_chan)) ) { + // get the channel only when it's not claimed yet. + spi_dma_chan_enabled |= DMA_CHANNEL_ENABLED(dma_chan); + ret = true; + } + periph_module_enable( PERIPH_SPI_DMA_MODULE ); + portEXIT_CRITICAL(&spi_dma_spinlock); + + return ret; +} + +//--------------------------------------- +bool spi_lobo_dma_chan_free(int dma_chan) +{ + assert( dma_chan == 1 || dma_chan == 2 ); + assert( spi_dma_chan_enabled & DMA_CHANNEL_ENABLED(dma_chan) ); + + portENTER_CRITICAL(&spi_dma_spinlock); + spi_dma_chan_enabled &= ~DMA_CHANNEL_ENABLED(dma_chan); + if ( spi_dma_chan_enabled == 0 ) { + //disable the DMA only when all the channels are freed. + periph_module_disable( PERIPH_SPI_DMA_MODULE ); + } + portEXIT_CRITICAL(&spi_dma_spinlock); + + return true; +} + + +//====================================================================================================== + + +//---------------------------------------------------------------------------------------------------------------- +static esp_err_t spi_lobo_bus_initialize(spi_lobo_host_device_t host, spi_lobo_bus_config_t *bus_config, int init) +{ + bool native=true, spi_chan_claimed, dma_chan_claimed; + + if (init > 0) { + /* ToDo: remove this when we have flash operations cooperating with this */ + SPI_CHECK(host!=SPI_HOST, "SPI1 is not supported", ESP_ERR_NOT_SUPPORTED); + + SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); + SPI_CHECK(spihost[host]==NULL, "host already in use", ESP_ERR_INVALID_STATE); + } + else { + SPI_CHECK(spihost[host]!=NULL, "host not in use", ESP_ERR_INVALID_STATE); + } + + SPI_CHECK(bus_config->mosi_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->mosi_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->sclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->miso_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->quadwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->quadhd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG); + + if (init > 0) { + spi_chan_claimed=spi_lobo_periph_claim(host); + SPI_CHECK(spi_chan_claimed, "host already in use", ESP_ERR_INVALID_STATE); + + //spihost[host]=malloc(sizeof(spi_lobo_host_t)); + spihost[host]=heap_caps_malloc(sizeof(spi_lobo_host_t), MALLOC_CAP_DMA); + if (spihost[host]==NULL) return ESP_ERR_NO_MEM; + memset(spihost[host], 0, sizeof(spi_lobo_host_t)); + // Create semaphore + spihost[host]->spi_lobo_bus_mutex = xSemaphoreCreateMutex(); + if (!spihost[host]->spi_lobo_bus_mutex) return ESP_ERR_NO_MEM; + } + + spihost[host]->cur_device = -1; + memcpy(&spihost[host]->cur_bus_config, bus_config, sizeof(spi_lobo_bus_config_t)); + + //Check if the selected pins correspond to the native pins of the peripheral + if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num!=io_signal[host].spid_native) native=false; + if (bus_config->miso_io_num >= 0 && bus_config->miso_io_num!=io_signal[host].spiq_native) native=false; + if (bus_config->sclk_io_num >= 0 && bus_config->sclk_io_num!=io_signal[host].spiclk_native) native=false; + if (bus_config->quadwp_io_num >= 0 && bus_config->quadwp_io_num!=io_signal[host].spiwp_native) native=false; + if (bus_config->quadhd_io_num >= 0 && bus_config->quadhd_io_num!=io_signal[host].spihd_native) native=false; + + spihost[host]->no_gpio_matrix=native; + if (native) { + //All SPI native pin selections resolve to 1, so we put that here instead of trying to figure + //out which FUNC_GPIOx_xSPIxx to grab; they all are defined to 1 anyway. + if (bus_config->mosi_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1); + if (bus_config->miso_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1); + if (bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1); + if (bus_config->quadhd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1); + if (bus_config->sclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1); + } else { + //Use GPIO + if (bus_config->mosi_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->mosi_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->mosi_io_num, io_signal[host].spid_out, false, false); + gpio_matrix_in(bus_config->mosi_io_num, io_signal[host].spid_in, false); + } + if (bus_config->miso_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->miso_io_num, GPIO_MODE_INPUT); + gpio_matrix_out(bus_config->miso_io_num, io_signal[host].spiq_out, false, false); + gpio_matrix_in(bus_config->miso_io_num, io_signal[host].spiq_in, false); + } + if (bus_config->quadwp_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->quadwp_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->quadwp_io_num, io_signal[host].spiwp_out, false, false); + gpio_matrix_in(bus_config->quadwp_io_num, io_signal[host].spiwp_in, false); + } + if (bus_config->quadhd_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->quadhd_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->quadhd_io_num, io_signal[host].spihd_out, false, false); + gpio_matrix_in(bus_config->quadhd_io_num, io_signal[host].spihd_in, false); + } + if (bus_config->sclk_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->sclk_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false); + } + } + periph_module_enable(io_signal[host].module); + spihost[host]->hw=io_signal[host].hw; + + if (init > 0) { + dma_chan_claimed=spi_lobo_dma_chan_claim(init); + if ( !dma_chan_claimed ) { + spi_lobo_periph_free( host ); + SPI_CHECK(dma_chan_claimed, "dma channel already in use", ESP_ERR_INVALID_STATE); + } + spihost[host]->dma_chan = init; + //See how many dma descriptors we need and allocate them + int dma_desc_ct=(bus_config->max_transfer_sz+SPI_MAX_DMA_LEN-1)/SPI_MAX_DMA_LEN; + if (dma_desc_ct==0) dma_desc_ct=1; //default to 4k when max is not given + spihost[host]->max_transfer_sz = dma_desc_ct*SPI_MAX_DMA_LEN; + + spihost[host]->dmadesc_tx=heap_caps_malloc(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA); + spihost[host]->dmadesc_rx=heap_caps_malloc(sizeof(lldesc_t)*dma_desc_ct, MALLOC_CAP_DMA); + if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) goto nomem; + + //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. + spi_lobo_dmaworkaround_idle(spihost[host]->dma_chan); + + // Reset DMA + spihost[host]->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; + spihost[host]->hw->dma_out_link.start=0; + spihost[host]->hw->dma_in_link.start=0; + spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); + spihost[host]->hw->dma_conf.out_data_burst_en=1; + + //Reset timing + spihost[host]->hw->ctrl2.val=0; + + //Disable unneeded ints + spihost[host]->hw->slave.rd_buf_done=0; + spihost[host]->hw->slave.wr_buf_done=0; + spihost[host]->hw->slave.rd_sta_done=0; + spihost[host]->hw->slave.wr_sta_done=0; + spihost[host]->hw->slave.rd_buf_inten=0; + spihost[host]->hw->slave.wr_buf_inten=0; + spihost[host]->hw->slave.rd_sta_inten=0; + spihost[host]->hw->slave.wr_sta_inten=0; + + //Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as + //disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling + //any transactions that are queued. + spihost[host]->hw->slave.trans_inten=1; + spihost[host]->hw->slave.trans_done=1; + + //Select DMA channel. + DPORT_SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, init, (host * 2)); + } + return ESP_OK; + +nomem: + if (spihost[host]) { + free(spihost[host]->dmadesc_tx); + free(spihost[host]->dmadesc_rx); + } + free(spihost[host]); + spi_lobo_periph_free(host); + return ESP_ERR_NO_MEM; +} + +//--------------------------------------------------------------------------- +static esp_err_t spi_lobo_bus_free(spi_lobo_host_device_t host, int dofree) +{ + if ((host == SPI_HOST) || (host >VSPI_HOST)) return ESP_ERR_NOT_SUPPORTED; // invalid host + + if (spihost[host] == NULL) return ESP_ERR_INVALID_STATE; // host not in use + + if (dofree) { + for (int x=0; xdevice[x] != NULL) return ESP_ERR_INVALID_STATE; // not all devices freed + } + } + if ( spihost[host]->dma_chan > 0 ) { + spi_lobo_dma_chan_free ( spihost[host]->dma_chan ); + } + spihost[host]->hw->slave.trans_inten=0; + spihost[host]->hw->slave.trans_done=0; + spi_lobo_periph_free(host); + + if (dofree) { + vSemaphoreDelete(spihost[host]->spi_lobo_bus_mutex); + free(spihost[host]->dmadesc_tx); + free(spihost[host]->dmadesc_rx); + free(spihost[host]); + spihost[host] = NULL; + } + return ESP_OK; +} + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +esp_err_t spi_lobo_bus_add_device(spi_lobo_host_device_t host, spi_lobo_bus_config_t *bus_config, spi_lobo_device_interface_config_t *dev_config, spi_lobo_device_handle_t *handle) +{ + if ((host == SPI_HOST) || (host >VSPI_HOST)) return ESP_ERR_NOT_SUPPORTED; // invalid host + + if (spihost[host] == NULL) { + esp_err_t ret = spi_lobo_bus_initialize(host, bus_config, 1); + if (ret) return ret; + } + + int freecs, maxdev; + int apbclk=APB_CLK_FREQ; + + if (spihost[host] == NULL) return ESP_ERR_INVALID_STATE; + + if (dev_config->spics_io_num >= 0) { + if (!GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num)) return ESP_ERR_INVALID_ARG; + if (dev_config->spics_ext_io_num > 0) dev_config->spics_ext_io_num = -1; + } + else { + //if ((dev_config->spics_ext_io_num <= 0) || (!GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_ext_io_num))) return ESP_ERR_INVALID_ARG; + } + + //ToDo: Check if some other device uses the same 'spics_ext_io_num' + + if (dev_config->clock_speed_hz == 0) return ESP_ERR_INVALID_ARG; + if (dev_config->spics_io_num > 0) maxdev = NO_CS; + else maxdev = NO_DEV; + + for (freecs=0; freecsdevice[freecs], NULL, (spi_lobo_device_t *)1)) break; + } + if (freecs == maxdev) return ESP_ERR_NOT_FOUND; + + // The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full + // duplex mode does absolutely nothing on the ESP32. + if ((dev_config->cs_ena_pretrans != 0) && (dev_config->flags & SPI_DEVICE_HALFDUPLEX)) return ESP_ERR_INVALID_ARG; + + // Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections. + if (((dev_config->flags & SPI_DEVICE_HALFDUPLEX)==0) && (dev_config->clock_speed_hz > ((apbclk*2)/5)) && (!spihost[host]->no_gpio_matrix)) return ESP_ERR_INVALID_ARG; + + //Allocate memory for device + spi_lobo_device_t *dev=malloc(sizeof(spi_lobo_device_t)); + if (dev==NULL) return ESP_ERR_NO_MEM; + + memset(dev, 0, sizeof(spi_lobo_device_t)); + spihost[host]->device[freecs]=dev; + + if (dev_config->duty_cycle_pos==0) dev_config->duty_cycle_pos=128; + dev->host=spihost[host]; + dev->host_dev = host; + + //We want to save a copy of the dev config in the dev struct. + memcpy(&dev->cfg, dev_config, sizeof(spi_lobo_device_interface_config_t)); + //We want to save a copy of the bus config in the dev struct. + memcpy(&dev->bus_config, bus_config, sizeof(spi_lobo_bus_config_t)); + + //Set CS pin, CS options + if (dev_config->spics_io_num > 0) { + if (spihost[host]->no_gpio_matrix &&dev_config->spics_io_num == io_signal[host].spics0_native && freecs==0) { + //Again, the cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define. + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], 1); + } else { + //Use GPIO matrix + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], PIN_FUNC_GPIO); + gpio_set_direction(dev_config->spics_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(dev_config->spics_io_num, io_signal[host].spics_out[freecs], false, false); + } + } + else if (dev_config->spics_ext_io_num >= 0) { + gpio_set_direction(dev_config->spics_ext_io_num, GPIO_MODE_OUTPUT); + gpio_set_level(dev_config->spics_ext_io_num, 1); + } + if (dev_config->flags & SPI_DEVICE_CLK_AS_CS) { + spihost[host]->hw->pin.master_ck_sel |= (1<hw->pin.master_ck_sel &= (1<flags & SPI_DEVICE_POSITIVE_CS) { + spihost[host]->hw->pin.master_cs_pol |= (1<hw->pin.master_cs_pol &= (1<host->device[x] == handle) handle->host->device[x]=NULL; + } + + // Check if all devices are removed from this host and free the bus if yes + for (x=0; xhost_dev]->device[x] !=NULL) break; + } + if (x == NO_DEV) { + free(handle); + spi_lobo_bus_free(handle->host_dev, 1); + } + else free(handle); + + return ESP_OK; +} + +//----------------------------------------------------------------- +static int IRAM_ATTR spi_freq_for_pre_n(int fapb, int pre, int n) { + return (fapb / (pre * n)); +} + +/* + * Set the SPI clock to a certain frequency. Returns the effective frequency set, which may be slightly + * different from the requested frequency. + */ +//----------------------------------------------------------------------------------- +static int IRAM_ATTR spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { + int pre, n, h, l, eff_clk; + + //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. + if (hz>((fapb/4)*3)) { + //Using Fapb directly will give us the best result here. + hw->clock.clkcnt_l=0; + hw->clock.clkcnt_h=0; + hw->clock.clkcnt_n=0; + hw->clock.clkdiv_pre=0; + hw->clock.clk_equ_sysclk=1; + eff_clk=fapb; + } else { + //For best duty cycle resolution, we want n to be as close to 32 as possible, but + //we also need a pre/n combo that gets us as close as possible to the intended freq. + //To do this, we bruteforce n and calculate the best pre to go along with that. + //If there's a choice between pre/n combos that give the same result, use the one + //with the higher n. + int bestn=-1; + int bestpre=-1; + int besterr=0; + int errval; + for (n=1; n<=64; n++) { + //Effectively, this does pre=round((fapb/n)/hz). + pre=((fapb/n)+(hz/2))/hz; + if (pre<=0) pre=1; + if (pre>8192) pre=8192; + errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); + if (bestn==-1 || errval<=besterr) { + besterr=errval; + bestn=n; + bestpre=pre; + } + } + + n=bestn; + pre=bestpre; + l=n; + //This effectively does round((duty_cycle*n)/256) + h=(duty_cycle*n+127)/256; + if (h<=0) h=1; + + hw->clock.clk_equ_sysclk=0; + hw->clock.clkcnt_n=n-1; + hw->clock.clkdiv_pre=pre-1; + hw->clock.clkcnt_h=h-1; + hw->clock.clkcnt_l=l-1; + eff_clk=spi_freq_for_pre_n(fapb, pre, n); + } + return eff_clk; +} + + + +//------------------------------------------------------------------------------------ +esp_err_t IRAM_ATTR spi_lobo_device_select(spi_lobo_device_handle_t handle, int force) +{ + if (handle == NULL) return ESP_ERR_INVALID_ARG; + + if ((handle->cfg.selected == 1) && (!force)) return ESP_OK; // already selected + + int i; + spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; + + // find device's host bus + for (i=0; idevice[i] == handle) break; + } + if (i == NO_DEV) return ESP_ERR_INVALID_ARG; + + if (!(xSemaphoreTake(host->spi_lobo_bus_mutex, SPI_SEMAPHORE_WAIT))) return ESP_ERR_INVALID_STATE; + + // Check if previously used device's bus device is the same + if (memcmp(&host->cur_bus_config, &handle->bus_config, sizeof(spi_lobo_bus_config_t)) != 0) { + // device has different bus configuration, we need to reconfigure the bus + esp_err_t err = spi_lobo_bus_free(1, 0); + if (err) { + xSemaphoreGive(host->spi_lobo_bus_mutex); + return err; + } + err = spi_lobo_bus_initialize(i, &handle->bus_config, -1); + if (err) { + xSemaphoreGive(host->spi_lobo_bus_mutex); + return err; + } + } + + //Reconfigure according to device settings, but only if the device changed or forced. + if ((force) || (host->device[host->cur_device] != handle)) { + //Assumes a hardcoded 80MHz Fapb for now. ToDo: figure out something better once we have clock scaling working. + int apbclk=APB_CLK_FREQ; + + //Speeds >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections. + if (((handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) == 0) && (handle->cfg.clock_speed_hz > ((apbclk*2)/5)) && (!host->no_gpio_matrix)) { + // set speed to 32 MHz + handle->cfg.clock_speed_hz = (apbclk*2)/5; + } + + int effclk=spi_set_clock(host->hw, apbclk, handle->cfg.clock_speed_hz, handle->cfg.duty_cycle_pos); + //Configure bit order + host->hw->ctrl.rd_bit_order=(handle->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0; + host->hw->ctrl.wr_bit_order=(handle->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0; + + //Configure polarity + //SPI iface needs to be configured for a delay in some cases. + int nodelay=0; + int extra_dummy=0; + if (host->no_gpio_matrix) { + if (effclk >= apbclk/2) { + nodelay=1; + } + } else { + if (effclk >= apbclk/2) { + nodelay=1; + extra_dummy=1; //Note: This only works on half-duplex connections. spi_lobo_bus_add_device checks for this. + } else if (effclk >= apbclk/4) { + nodelay=1; + } + } + if (handle->cfg.mode==0) { + host->hw->pin.ck_idle_edge=0; + host->hw->user.ck_out_edge=0; + host->hw->ctrl2.miso_delay_mode=nodelay?0:2; + } else if (handle->cfg.mode==1) { + host->hw->pin.ck_idle_edge=0; + host->hw->user.ck_out_edge=1; + host->hw->ctrl2.miso_delay_mode=nodelay?0:1; + } else if (handle->cfg.mode==2) { + host->hw->pin.ck_idle_edge=1; + host->hw->user.ck_out_edge=1; + host->hw->ctrl2.miso_delay_mode=nodelay?0:1; + } else if (handle->cfg.mode==3) { + host->hw->pin.ck_idle_edge=1; + host->hw->user.ck_out_edge=0; + host->hw->ctrl2.miso_delay_mode=nodelay?0:2; + } + + //Configure bit sizes, load addr and command + host->hw->user.usr_dummy=(handle->cfg.dummy_bits+extra_dummy)?1:0; + host->hw->user.usr_addr=(handle->cfg.address_bits)?1:0; + host->hw->user.usr_command=(handle->cfg.command_bits)?1:0; + host->hw->user1.usr_addr_bitlen=handle->cfg.address_bits-1; + host->hw->user1.usr_dummy_cyclelen=handle->cfg.dummy_bits+extra_dummy-1; + host->hw->user2.usr_command_bitlen=handle->cfg.command_bits-1; + //Configure misc stuff + host->hw->user.doutdin=(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1; + host->hw->user.sio=(handle->cfg.flags & SPI_DEVICE_3WIRE)?1:0; + + host->hw->ctrl2.setup_time=handle->cfg.cs_ena_pretrans-1; + host->hw->user.cs_setup=handle->cfg.cs_ena_pretrans?1:0; + host->hw->ctrl2.hold_time=handle->cfg.cs_ena_posttrans-1; + host->hw->user.cs_hold=(handle->cfg.cs_ena_posttrans)?1:0; + + //Configure CS pin + host->hw->pin.cs0_dis=(i==0)?0:1; + host->hw->pin.cs1_dis=(i==1)?0:1; + host->hw->pin.cs2_dis=(i==2)?0:1; + + host->cur_device = i; + } + + if ((handle->cfg.spics_io_num < 0) && (handle->cfg.spics_ext_io_num > 0)) { + gpio_set_level(handle->cfg.spics_ext_io_num, 0); + } + + handle->cfg.selected = 1; + + return ESP_OK; +} + +//--------------------------------------------------------------------------- +esp_err_t IRAM_ATTR spi_lobo_device_deselect(spi_lobo_device_handle_t handle) +{ + if (handle == NULL) return ESP_ERR_INVALID_ARG; + + if (handle->cfg.selected == 0) return ESP_OK; // already deselected + + int i; + spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; + + for (i=0; idevice[i] == handle) break; + } + if (i == NO_DEV) return ESP_ERR_INVALID_ARG; + + if (host->device[host->cur_device] == handle) { + if ((handle->cfg.spics_io_num < 0) && (handle->cfg.spics_ext_io_num > 0)) { + gpio_set_level(handle->cfg.spics_ext_io_num, 1); + } + } + + handle->cfg.selected = 0; + xSemaphoreGive(host->spi_lobo_bus_mutex); + + return ESP_OK; +} + +//-------------------------------------------------------------------------------- +esp_err_t IRAM_ATTR spi_lobo_device_TakeSemaphore(spi_lobo_device_handle_t handle) +{ + if (!(xSemaphoreTake(handle->host->spi_lobo_bus_mutex, SPI_SEMAPHORE_WAIT))) return ESP_ERR_INVALID_STATE; + else return ESP_OK; +} + +//--------------------------------------------------------------------------- +void IRAM_ATTR spi_lobo_device_GiveSemaphore(spi_lobo_device_handle_t handle) +{ + xSemaphoreTake(handle->host->spi_lobo_bus_mutex, portMAX_DELAY); +} + +//---------------------------------------------------------- +uint32_t spi_lobo_get_speed(spi_lobo_device_handle_t handle) +{ + spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; + uint32_t speed = 0; + if (spi_lobo_device_select(handle, 0) == ESP_OK) { + if (host->hw->clock.clk_equ_sysclk == 1) speed = 80000000; + else speed = 80000000/(host->hw->clock.clkdiv_pre+1)/(host->hw->clock.clkcnt_n+1); + } + spi_lobo_device_deselect(handle); + return speed; +} + +//-------------------------------------------------------------------------- +uint32_t spi_lobo_set_speed(spi_lobo_device_handle_t handle, uint32_t speed) +{ + spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; + uint32_t newspeed = 0; + if (spi_lobo_device_select(handle, 0) == ESP_OK) { + spi_lobo_device_deselect(handle); + handle->cfg.clock_speed_hz = speed; + if (spi_lobo_device_select(handle, 1) == ESP_OK) { + if (host->hw->clock.clk_equ_sysclk == 1) newspeed = 80000000; + else newspeed = 80000000/(host->hw->clock.clkdiv_pre+1)/(host->hw->clock.clkcnt_n+1); + } + } + spi_lobo_device_deselect(handle); + + return newspeed; +} + +//------------------------------------------------------------- +bool spi_lobo_uses_native_pins(spi_lobo_device_handle_t handle) +{ + return handle->host->no_gpio_matrix; +} + +//------------------------------------------------------------------- +void spi_lobo_get_native_pins(int host, int *sdi, int *sdo, int *sck) +{ + *sdo = io_signal[host].spid_native; + *sdi = io_signal[host].spiq_native; + *sck = io_signal[host].spiclk_native; +} + +/* +When using 'spi_lobo_transfer_data' function we can have several scenarios: + +A: Send only (trans->rxlength = 0) +B: Receive only (trans->txlength = 0) +C: Send & receive (trans->txlength > 0 & trans->rxlength > 0) +D: No operation (trans->txlength = 0 & trans->rxlength = 0) + +*/ +//---------------------------------------------------------------------------------------------------------- +esp_err_t IRAM_ATTR spi_lobo_transfer_data(spi_lobo_device_handle_t handle, spi_lobo_transaction_t *trans) { + if (!handle) return ESP_ERR_INVALID_ARG; + + // *** For now we can only handle 8-bit bytes transmission + if (((trans->length % 8) != 0) || ((trans->rxlength % 8) != 0)) return ESP_ERR_INVALID_ARG; + + spi_lobo_host_t *host=(spi_lobo_host_t*)handle->host; + esp_err_t ret; + uint8_t do_deselect = 0; + const uint8_t *txbuffer = NULL; + uint8_t *rxbuffer = NULL; + + if (trans->flags & SPI_TRANS_USE_TXDATA) { + // Send data from 'trans->tx_data' + txbuffer=(uint8_t*)&trans->tx_data[0]; + } else { + // Send data from 'trans->tx_buffer' + txbuffer=(uint8_t*)trans->tx_buffer; + } + if (trans->flags & SPI_TRANS_USE_RXDATA) { + // Receive data to 'trans->rx_data' + rxbuffer=(uint8_t*)&trans->rx_data[0]; + } else { + // Receive data to 'trans->rx_buffer' + rxbuffer=(uint8_t*)trans->rx_buffer; + } + + // ** Set transmit & receive length in bytes + uint32_t txlen = trans->length / 8; + uint32_t rxlen = trans->rxlength / 8; + + if (txbuffer == NULL) txlen = 0; + if (rxbuffer == NULL) rxlen = 0; + if ((rxlen == 0) && (txlen == 0)) { + // ** NOTHING TO SEND or RECEIVE, return + return ESP_ERR_INVALID_ARG; + } + + // If using 'trans->tx_data' and/or 'trans->rx_data', maximum 4 bytes can be sent/received + if ((txbuffer == &trans->tx_data[0]) && (txlen > 4)) return ESP_ERR_INVALID_ARG; + if ((rxbuffer == &trans->rx_data[0]) && (rxlen > 4)) return ESP_ERR_INVALID_ARG; + + // --- Wait for SPI bus ready --- + while (host->hw->cmd.usr); + + // ** If the device was not selected, select it + if (handle->cfg.selected == 0) { + ret = spi_lobo_device_select(handle, 0); + if (ret) return ret; + do_deselect = 1; // We will deselect the device after the operation ! + } + + // ** Call pre-transmission callback, if any + if (handle->cfg.pre_cb) handle->cfg.pre_cb(trans); + + // Test if operating in full duplex mode + uint8_t duplex = 1; + if (handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) duplex = 0; // Half duplex mode ! + + uint32_t bits, rdbits; + uint32_t wd; + uint8_t bc, rdidx; + uint32_t rdcount = rxlen; // Total number of bytes to read + uint32_t count = 0; // number of bytes transmitted + uint32_t rd_read = 0; // Number of bytes read so far + + host->hw->user.usr_mosi_highpart = 0; // use the whole spi buffer + + // ** Check if address phase will be used + host->hw->user2.usr_command_value=trans->command; + if (handle->cfg.address_bits>32) { + host->hw->addr=trans->address >> 32; + host->hw->slv_wr_status=trans->address & 0xffffffff; + } else { + host->hw->addr=trans->address & 0xffffffff; + } + + // Check if we have to transmit some data + if (txlen > 0) { + host->hw->user.usr_mosi = 1; + uint8_t idx; + bits = 0; // remaining bits to send + idx = 0; // index to spi hw data_buf (16 32-bit words, 64 bytes, 512 bits) + + // ** Transmit 'txlen' bytes + while (count < txlen) { + wd = 0; + for (bc=0;bc<32;bc+=8) { + wd |= (uint32_t)txbuffer[count] << bc; + count++; // Increment sent data count + bits += 8; // Increment bits count + if (count == txlen) break; // If all transmit data pushed to hw spi buffer break from the loop + } + host->hw->data_buf[idx] = wd; + idx++; + if (idx == 16) { + // hw SPI buffer full (all 64 bytes filled, START THE TRANSSACTION + host->hw->mosi_dlen.usr_mosi_dbitlen=bits-1; // Set mosi dbitlen + + if ((duplex) && (rdcount > 0)) { + // In full duplex mode we are receiving while sending ! + host->hw->miso_dlen.usr_miso_dbitlen = bits-1; // Set miso dbitlen + host->hw->user.usr_miso = 1; + } + else { + host->hw->miso_dlen.usr_miso_dbitlen = 0; // In half duplex mode nothing will be received + host->hw->user.usr_miso = 0; + } + + // ** Start the transaction *** + host->hw->cmd.usr=1; + // Wait the transaction to finish + while (host->hw->cmd.usr); + + if ((duplex) && (rdcount > 0)) { + // *** in full duplex mode transfer received data to input buffer *** + rdidx = 0; + while (bits > 0) { + wd = host->hw->data_buf[rdidx]; + rdidx++; + for (bc=0;bc<32;bc+=8) { // get max 4 bytes + rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); + rdcount--; + bits -= 8; + if (rdcount == 0) { + bits = 0; + break; // Finished reading data + } + } + } + } + bits = 0; // nothing in hw spi buffer yet + idx = 0; // start from the beginning of the hw spi buffer + } + } + // *** All transmit data are sent or pushed to hw spi buffer + // bits > 0 IF THERE ARE SOME DATA STILL WAITING IN THE HW SPI TRANSMIT BUFFER + if (bits > 0) { + // ** WE HAVE SOME DATA IN THE HW SPI TRANSMIT BUFFER + host->hw->mosi_dlen.usr_mosi_dbitlen = bits-1; // Set mosi dbitlen + + if ((duplex) && (rdcount > 0)) { + // In full duplex mode we are receiving while sending ! + host->hw->miso_dlen.usr_miso_dbitlen = bits-1; // Set miso dbitlen + host->hw->user.usr_miso = 1; + } + else { + host->hw->miso_dlen.usr_miso_dbitlen = 0; // In half duplex mode nothing will be received + host->hw->user.usr_miso = 0; + } + + // ** Start the transaction *** + host->hw->cmd.usr=1; + // Wait the transaction to finish + while (host->hw->cmd.usr); + + if ((duplex) && (rdcount > 0)) { + // *** in full duplex mode transfer received data to input buffer *** + rdidx = 0; + while (bits > 0) { + wd = host->hw->data_buf[rdidx]; + rdidx++; + for (bc=0;bc<32;bc+=8) { // get max 4 bytes + rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); + rdcount--; + bits -= 8; + if (bits == 0) break; + if (rdcount == 0) { + bits = 0; + break; // Finished reading data + } + } + } + } + } + //if (duplex) rdcount = 0; // In duplex mode receive only as many bytes as was transmitted + } + + // ------------------------------------------------------------------------ + // *** If rdcount = 0 we have nothing to receive and we exit the function + // This is true if no data receive was requested, + // or all the data was received in Full duplex mode during the transmission + // ------------------------------------------------------------------------ + if (rdcount > 0) { + // ---------------------------------------------------------------------------------------------------------------- + // *** rdcount > 0, we have to receive some data + // This is true if we operate in Half duplex mode when receiving after transmission is done, + // or not all data was received in Full duplex mode during the transmission (trans->rxlength > trans->txlength) + // ---------------------------------------------------------------------------------------------------------------- + host->hw->user.usr_mosi = 0; // do not send + host->hw->user.usr_miso = 1; // do receive + while (rdcount > 0) { + if (rdcount <= 64) rdbits = rdcount * 8; + else rdbits = 64 * 8; + + // Load receive buffer + host->hw->mosi_dlen.usr_mosi_dbitlen=0; + host->hw->miso_dlen.usr_miso_dbitlen=rdbits-1; + + // ** Start the transaction *** + host->hw->cmd.usr=1; + // Wait the transaction to finish + while (host->hw->cmd.usr); + + // *** transfer received data to input buffer *** + rdidx = 0; + while (rdbits > 0) { + wd = host->hw->data_buf[rdidx]; + rdidx++; + for (bc=0;bc<32;bc+=8) { + rxbuffer[rd_read++] = (uint8_t)((wd >> bc) & 0xFF); + rdcount--; + rdbits -= 8; + if (rdcount == 0) { + rdbits = 0; + break; + } + } + } + } + } + + // ** Call post-transmission callback, if any + if (handle->cfg.post_cb) handle->cfg.post_cb(trans); + + if (do_deselect) { + // Spi device was selected in this function, we have to deselect it now + ret = spi_lobo_device_deselect(handle); + if (ret) return ret; + } + + return ESP_OK; +} diff --git a/components/spidriver/spi_master_lobo.h b/components/spidriver/spi_master_lobo.h new file mode 100644 index 0000000..863ba82 --- /dev/null +++ b/components/spidriver/spi_master_lobo.h @@ -0,0 +1,355 @@ +// Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#ifndef _DRIVER_SPI_MASTER_LOBO_H_ +#define _DRIVER_SPI_MASTER_LOBO_H_ + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "soc/spi_struct.h" + +#include "esp_intr.h" +#include "esp_intr_alloc.h" +#include "rom/lldesc.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +//Maximum amount of bytes that can be put in one DMA descriptor +#define SPI_MAX_DMA_LEN (4096-4) + +/** + * @brief Enum with the three SPI peripherals that are software-accessible in it + */ +typedef enum { + SPI_HOST=0, ///< SPI1, SPI; Cannot be used in this driver! + HSPI_HOST=1, ///< SPI2, HSPI + VSPI_HOST=2 ///< SPI3, VSPI +} spi_lobo_host_device_t; + + +/** + * @brief This is a configuration structure for a SPI bus. + * + * You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the + * GPIO matrix to route the signals. An exception is made when all signals either can be routed through + * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds. + */ +typedef struct { + int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used. + int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used. + int sclk_io_num; ///< GPIO pin for Spi CLocK signal, or -1 if not used. + int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used. + int quadhd_io_num; ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used. + int max_transfer_sz; ///< Maximum transfer size, in bytes. Defaults to 4094 if 0. +} spi_lobo_bus_config_t; + + +#define SPI_DEVICE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first +#define SPI_DEVICE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first +#define SPI_DEVICE_BIT_LSBFIRST (SPI_TXBIT_LSBFIRST|SPI_RXBIT_LSBFIRST); ///< Transmit and receive LSB first +#define SPI_DEVICE_3WIRE (1<<2) ///< Use spiq for both sending and receiving data +#define SPI_DEVICE_POSITIVE_CS (1<<3) ///< Make CS positive during a transaction instead of negative +#define SPI_DEVICE_HALFDUPLEX (1<<4) ///< Transmit data before receiving it, instead of simultaneously +#define SPI_DEVICE_CLK_AS_CS (1<<5) ///< Output clock on CS line if CS is active + +#define SPI_ERR_OTHER_CONFIG 7001 + +typedef struct spi_lobo_transaction_t spi_lobo_transaction_t; +typedef void(*transaction_cb_t)(spi_lobo_transaction_t *trans); + +/** + * @brief This is a configuration for a SPI slave device that is connected to one of the SPI buses. + */ +typedef struct { + uint8_t command_bits; ///< Amount of bits in command phase (0-16) + uint8_t address_bits; ///< Amount of bits in address phase (0-64) + uint8_t dummy_bits; ///< Amount of dummy bits to insert between address and data phase + uint8_t mode; ///< SPI mode (0-3) + uint8_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128. + uint8_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions. + uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16) + int clock_speed_hz; ///< Clock speed, in Hz + int spics_io_num; ///< CS GPIO pin for this device, handled by hardware; set to -1 if not used + int spics_ext_io_num; ///< CS GPIO pin for this device, handled by software (spi_lobo_device_select/spi_lobo_device_deselect); only used if spics_io_num=-1 + uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags + transaction_cb_t pre_cb; ///< Callback to be called before a transmission is started. This callback from 'spi_lobo_transfer_data' function. + transaction_cb_t post_cb; ///< Callback to be called after a transmission has completed. This callback from 'spi_lobo_transfer_data' function. + uint8_t selected; ///< **INTERNAL** 1 if the device's CS pin is active +} spi_lobo_device_interface_config_t; + + +#define SPI_TRANS_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode +#define SPI_TRANS_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode +#define SPI_TRANS_MODE_DIOQIO_ADDR (1<<2) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO +#define SPI_TRANS_USE_RXDATA (1<<3) ///< Receive into rx_data member of spi_lobo_transaction_t instead into memory at rx_buffer. +#define SPI_TRANS_USE_TXDATA (1<<4) ///< Transmit tx_data member of spi_lobo_transaction_t instead of data at tx_buffer. Do not set tx_buffer when using this. + +/** + * This structure describes one SPI transmission + */ +struct spi_lobo_transaction_t { + uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flags + uint16_t command; ///< Command data. Specific length was given when device was added to the bus. + uint64_t address; ///< Address. Specific length was given when device was added to the bus. + size_t length; ///< Total data length to be transmitted to the device, in bits; if 0, no data is transmitted + size_t rxlength; ///< Total data length to be received from the device, in bits; if 0, no data is received + void *user; ///< User-defined variable. Can be used to store eg transaction ID or data to be used by pre_cb and/or post_cb callbacks. + union { + const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase + uint8_t tx_data[4]; ///< If SPI_USE_TXDATA is set, data set here is sent directly from this variable. + }; + union { + void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase + uint8_t rx_data[4]; ///< If SPI_USE_RXDATA is set, data is received directly to this variable + }; +}; + +#define NO_CS 3 // Number of CS pins per SPI host +#define NO_DEV 6 // Number of spi devices per SPI host; more than 3 devices can be attached to the same bus if using software CS's +#define SPI_SEMAPHORE_WAIT 2000 // Time in ms to wait for SPI mutex + +typedef struct spi_lobo_device_t spi_lobo_device_t; + +typedef struct { + spi_lobo_device_t *device[NO_DEV]; + intr_handle_t intr; + spi_dev_t *hw; + //spi_lobo_transaction_t *cur_trans; + int cur_device; + lldesc_t *dmadesc_tx; + lldesc_t *dmadesc_rx; + bool no_gpio_matrix; + int dma_chan; + int max_transfer_sz; + QueueHandle_t spi_lobo_bus_mutex; + spi_lobo_bus_config_t cur_bus_config; +} spi_lobo_host_t; + +struct spi_lobo_device_t { + spi_lobo_device_interface_config_t cfg; + spi_lobo_host_t *host; + spi_lobo_bus_config_t bus_config; + spi_lobo_host_device_t host_dev; +}; + +typedef spi_lobo_device_t* spi_lobo_device_handle_t; ///< Handle for a device on a SPI bus +typedef spi_lobo_host_t* spi_lobo_host_handle_t; +typedef spi_lobo_device_interface_config_t* spi_lobo_device_interface_config_handle_t; + + +/** + * @brief Add a device. This allocates a CS line for the device, allocates memory for the device structure and hooks + * up the CS pin to whatever is specified. + * + * This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master + * peripheral and routes it to the indicated GPIO. All SPI master devices have three hw CS pins and can thus control + * up to three devices. Software handled CS pin can also be used for additional devices on the same SPI bus. + * + * ### If selected SPI host device bus is not yet initialized, it is initialized first with 'bus_config' function ### + * + * @note While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are + * supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz. + * + * @param host SPI peripheral to allocate device on (HSPI or VSPI) + * @param dev_config SPI interface protocol config for the device + * @param bus_config Pointer to a spi_lobo_bus_config_t struct specifying how the host device bus should be initialized + * @param handle Pointer to variable to hold the device handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NOT_FOUND if host doesn't have any free CS slots + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t spi_lobo_bus_add_device(spi_lobo_host_device_t host, spi_lobo_bus_config_t *bus_config, spi_lobo_device_interface_config_t *dev_config, spi_lobo_device_handle_t *handle); + +/** + * @brief Remove a device from the SPI bus. If after removal no other device is attached to the spi bus device, it is freed. + * + * @param handle Device handle to free + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if device already is freed + * - ESP_OK on success + */ +esp_err_t spi_lobo_bus_remove_device(spi_lobo_device_handle_t handle); + + +/** + * @brief Return the actuall SPI bus speed for the spi device in Hz + * + * Some frequencies cannot be set, for example 30000000 will actually set SPI clock to 26666666 Hz + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * + * @return + * - actuall SPI clock + */ +uint32_t spi_lobo_get_speed(spi_lobo_device_handle_t handle); + +/** + * @brief Set the new clock speed for the device, return the actuall SPI bus speed set, in Hz + * This function can be used after the device is initialized + * + * Some frequencies cannot be set, for example 30000000 will actually set SPI clock to 26666666 Hz + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * @param speed New device spi clock to be set in Hz + * + * @return + * - actuall SPI clock + * - 0 if speed cannot be set + */ +uint32_t spi_lobo_set_speed(spi_lobo_device_handle_t handle, uint32_t speed); + +/** + * @brief Select spi device for transmission + * + * It configures spi bus with selected spi device parameters if previously selected device was different than the current + * If device's spics_io_num=-1 and spics_ext_io_num > 0 'spics_ext_io_num' pin is set to active state (low) + * + * spi bus device's semaphore is taken before selecting the device + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * @param force configure spi bus even if the previous device was the same + * + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_lobo_device_select(spi_lobo_device_handle_t handle, int force); + +/** + * @brief De-select spi device + * + * If device's spics_io_num=-1 and spics_ext_io_num > 0 'spics_ext_io_num' pin is set to inactive state (high) + * + * spi bus device's semaphore is given after selecting the device + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_lobo_device_deselect(spi_lobo_device_handle_t handle); + + +/** + * @brief Check if spi bus uses native spi pins + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * + * @return + * - true if native spi pins are used + * - false if spi pins are routed through gpio matrix + */ +bool spi_lobo_uses_native_pins(spi_lobo_device_handle_t handle); + +/** + * @brief Get spi bus native spi pins + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * + * @return + * places spi bus native pins in provided pointers + */ +void spi_lobo_get_native_pins(int host, int *sdi, int *sdo, int *sck); + +/** + * @brief Transimit and receive data to/from spi device based on transaction data + * + * TRANSMIT 8-bit data to spi device from 'trans->tx_buffer' or 'trans->tx_data' (trans->lenght/8 bytes) + * and RECEIVE data to 'trans->rx_buffer' or 'trans->rx_data' (trans->rx_length/8 bytes) + * Lengths must be 8-bit multiples! + * If trans->rx_buffer is NULL or trans->rx_length is 0, only transmits data + * If trans->tx_buffer is NULL or trans->length is 0, only receives data + * If the device is in duplex mode (SPI_DEVICE_HALFDUPLEX flag NOT set), data are transmitted and received simultaneously. + * If the device is in half duplex mode (SPI_DEVICE_HALFDUPLEX flag IS set), data are received after transmission + * 'address', 'command' and 'dummy bits' are transmitted before data phase IF set in device's configuration + * and IF 'trans->length' and 'trans->rx_length' are NOT both 0 + * If device was not previously selected, it will be selected before transmission and deselected after transmission. + * + * @param handle Device handle obtained using spi_lobo_bus_add_device + * + * @param trans Pointer to variable containing the description of the transaction that is executed + * + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP error code if device cannot be selected + * - ESP_OK on success + * + */ +esp_err_t spi_lobo_transfer_data(spi_lobo_device_handle_t handle, spi_lobo_transaction_t *trans); + + +/* + * SPI transactions uses the semaphore (taken in select function) to protect the transfer + */ +esp_err_t spi_lobo_device_TakeSemaphore(spi_lobo_device_handle_t handle); +void spi_lobo_device_GiveSemaphore(spi_lobo_device_handle_t handle); + + +/** + * @brief Setup a DMA link chain + * + * This routine will set up a chain of linked DMA descriptors in the array pointed to by + * ``dmadesc``. Enough DMA descriptors will be used to fit the buffer of ``len`` bytes in, and the + * descriptors will point to the corresponding positions in ``buffer`` and linked together. The + * end result is that feeding ``dmadesc[0]`` into DMA hardware results in the entirety ``len`` bytes + * of ``data`` being read or written. + * + * @param dmadesc Pointer to array of DMA descriptors big enough to be able to convey ``len`` bytes + * @param len Length of buffer + * @param data Data buffer to use for DMA transfer + * @param isrx True if data is to be written into ``data``, false if it's to be read from ``data``. + */ +void spi_lobo_setup_dma_desc_links(lldesc_t *dmadesc, int len, const uint8_t *data, bool isrx); + +/** + * @brief Check if a DMA reset is requested but has not completed yet + * + * @return True when a DMA reset is requested but hasn't completed yet. False otherwise. + */ +bool spi_lobo_dmaworkaround_reset_in_progress(); + + +/** + * @brief Mark a DMA channel as idle. + * + * A call to this function tells the workaround logic that this channel will + * not be affected by a global SPI DMA reset. + */ +void spi_lobo_dmaworkaround_idle(int dmachan); + +/** + * @brief Mark a DMA channel as active. + * + * A call to this function tells the workaround logic that this channel will + * be affected by a global SPI DMA reset, and a reset like that should not be attempted. + */ +void spi_lobo_dmaworkaround_transfer_active(int dmachan); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/spiffs/component.mk b/components/spiffs/component.mk new file mode 100644 index 0000000..3d6fbce --- /dev/null +++ b/components/spiffs/component.mk @@ -0,0 +1,7 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := . +COMPONENT_ADD_INCLUDEDIRS := . +COMPONENT_PRIV_INCLUDEDIRS := \ No newline at end of file diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c new file mode 100644 index 0000000..57b8aa0 --- /dev/null +++ b/components/spiffs/esp_spiffs.c @@ -0,0 +1,138 @@ +/* + * Lua RTOS, SPIFFS low access + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#include + +#include "esp_spiffs.h" +#include "esp_attr.h" + +#include "spiffs.h" + +#include + +s32_t IRAM_ATTR esp32_spi_flash_read(u32_t addr, u32_t size, u8_t *dst) { + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & (u32_t)-4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & (u32_t)-4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *)(((ptrdiff_t)buff + (4 - 1)) & (u32_t)-4); + + if (spi_flash_read(aaddr, (void *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(dst, abuff + (addr - aaddr), size); + + free(buff); + } else { + if (spi_flash_read(addr, (void *)dst, size) != 0) { + return SPIFFS_ERR_INTERNAL; + } + } + + return SPIFFS_OK; +} + +s32_t IRAM_ATTR esp32_spi_flash_write(u32_t addr, u32_t size, const u8_t *src) { + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & -4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & -4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *)(((ptrdiff_t)buff + (4 - 1)) & -4); + + if (spi_flash_read(aaddr, (void *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(abuff + (addr - aaddr), src, size); + + if (spi_flash_write(aaddr, (uint32_t *)abuff, asize) != 0) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + free(buff); + } else { + if (spi_flash_write(addr, (uint32_t *)src, size) != 0) { + return SPIFFS_ERR_INTERNAL; + } + } + + return SPIFFS_OK; +} + +s32_t IRAM_ATTR esp32_spi_flash_erase(u32_t addr, u32_t size) { + if (spi_flash_erase_sector(addr >> 12) != 0) { + return SPIFFS_ERR_INTERNAL; + } + + return SPIFFS_OK; +} diff --git a/components/spiffs/esp_spiffs.h b/components/spiffs/esp_spiffs.h new file mode 100644 index 0000000..c68e652 --- /dev/null +++ b/components/spiffs/esp_spiffs.h @@ -0,0 +1,43 @@ +/* + * Lua RTOS, write syscall implementation + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#ifndef __ESP_SPIFFS_H__ +#define __ESP_SPIFFS_H__ + +#include "spiffs.h" + +s32_t esp32_spi_flash_read(u32_t addr, u32_t size, u8_t *dst); +s32_t esp32_spi_flash_write(u32_t addr, u32_t size, const u8_t *src); +s32_t esp32_spi_flash_erase(u32_t addr, u32_t size); + +#define low_spiffs_read (spiffs_read *)esp32_spi_flash_read +#define low_spiffs_write (spiffs_write *)esp32_spi_flash_write +#define low_spiffs_erase (spiffs_erase *)esp32_spi_flash_erase + +#endif // __ESP_SPIFFS_H__ diff --git a/components/spiffs/list.c b/components/spiffs/list.c new file mode 100644 index 0000000..adf2fcb --- /dev/null +++ b/components/spiffs/list.c @@ -0,0 +1,251 @@ +/* + * Lua RTOS, list data structure + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#include "esp_attr.h" + +#include +#include +#include + +#include "list.h" +#include "mutex.h" + +void list_init(struct list *list, int first_index) { + // Create the mutex + mtx_init(&list->mutex, NULL, NULL, 0); + + mtx_lock(&list->mutex); + + list->indexes = 0; + list->free = NULL; + list->index = NULL; + list->first_index = first_index; + + mtx_unlock(&list->mutex); +} + +int list_add(struct list *list, void *item, int *item_index) { + struct list_index *index = NULL; + struct list_index *indexa = NULL; + int grow = 0; + + mtx_lock(&list->mutex); + + // Get an index + if (list->free) { + // Get first free element + index = list->free; + list->free = index->next; + } else { + // Must grow index array + grow = 1; + } + + if (grow) { + // Increment index count + list->indexes++; + + // Create a new index array for allocate new index + indexa = (struct list_index *)malloc(sizeof(struct list_index) * list->indexes); + if (!indexa) { + mtx_unlock(&list->mutex); + return ENOMEM; + } + + if (list->index) { + // Copy current index array to new created + bcopy(list->index, indexa, sizeof(struct list_index) * (list->indexes - 1)); + + // Free current index array + free(list->index); + } + + // Store new index array + list->index = indexa; + + // Current index + index = list->index + list->indexes - 1; + + // Initialize new index + index->index = list->indexes - 1; + + } + + index->next = NULL; + index->item = item; + index->deleted = 0; + + // Return index + *item_index = index->index + list->first_index; + + mtx_unlock(&list->mutex); + + return 0; +} + +int IRAM_ATTR list_get(struct list *list, int index, void **item) { + struct list_index *cindex = NULL; + int iindex; + + mtx_lock(&list->mutex); + + if (!list->indexes) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Get new internal index + iindex = index - list->first_index; + + // Test for a valid index + if (iindex > list->indexes) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + cindex = list->index + iindex; + + if (cindex->deleted) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + *item = cindex->item; + + mtx_unlock(&list->mutex); + + return 0; +} + +int list_remove(struct list *list, int index, int destroy) { + struct list_index *cindex = NULL; + int iindex; + + mtx_lock(&list->mutex); + + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Get new internal index + iindex = index - list->first_index; + + // Test for a valid index + if ((iindex < 0) || (iindex > list->indexes)) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + cindex = &list->index[iindex]; + + if (destroy) { + free(cindex->item); + } + + cindex->next = list->free; + cindex->deleted = 1; + list->free = cindex; + + mtx_unlock(&list->mutex); + + return 0; +} + +int IRAM_ATTR list_first(struct list *list) { + int index; + int res = -1; + + mtx_lock(&list->mutex); + + for(index=0;index < list->indexes;index++) { + if (!list->index[index].deleted) { + res = index + list->first_index; + break; + } + } + + mtx_unlock(&list->mutex); + + return res; +} + +int IRAM_ATTR list_next(struct list *list, int index) { + int res = -1; + int iindex; + + mtx_lock(&list->mutex); + + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return -1; + } + + // Get new internal index + iindex = index - list->first_index + 1; + + // Get next non deleted item on list + for(;iindex < list->indexes;iindex++) { + if (!list->index[iindex].deleted) { + res = iindex + list->first_index; + break; + } + } + + mtx_unlock(&list->mutex); + + return res; +} + +void list_destroy(struct list *list, int items) { + int index; + + mtx_lock(&list->mutex); + + if (items) { + for(index=0;index < list->indexes;index++) { + if (!list->index[index].deleted) { + free(list->index[index].item); + } + } + } + + free(list->index); + + mtx_unlock(&list->mutex); + mtx_destroy(&list->mutex); +} diff --git a/components/spiffs/list.h b/components/spiffs/list.h new file mode 100644 index 0000000..4f9c5b1 --- /dev/null +++ b/components/spiffs/list.h @@ -0,0 +1,60 @@ +/* + * Lua RTOS, list data structure + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + */ + +#ifndef _LIST_H +#define _LIST_H + +#include +#include "mutex.h" + +struct list { + struct mtx mutex; + struct list_index *index; + struct list_index *free; + uint8_t indexes; + uint8_t first_index; +}; + +struct list_index { + void *item; + uint8_t index; + uint8_t deleted; + struct list_index *next; +}; + +void list_init(struct list *list, int first_index); +int list_add(struct list *list, void *item, int *item_index); +int list_get(struct list *list, int index, void **item); +int list_remove(struct list *list, int index, int destroy); +int list_first(struct list *list); +int list_next(struct list *list, int index); +void list_destroy(struct list *list, int items); + +#endif /* LIST_H */ + diff --git a/components/spiffs/mutex.c b/components/spiffs/mutex.c new file mode 100644 index 0000000..84aea9c --- /dev/null +++ b/components/spiffs/mutex.c @@ -0,0 +1,102 @@ +/* + * Lua RTOS, mutex api implementation over FreeRTOS + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + * + * Modified by: LoBo (loboris@gmail.com / https://github.com/loboris) + * + */ + +#include "freertos/FreeRTOS.h" +#include "esp_attr.h" +#include "mutex.h" + + +#define portEND_SWITCHING_ISR(xSwitchRequired) \ +if (xSwitchRequired) { \ + _frxt_setup_switch(); \ +} + +extern unsigned port_interruptNesting[portNUM_PROCESSORS]; + +void _mtx_init() { +} + +void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts) { + mutex->sem = xSemaphoreCreateBinary(); + + if (mutex->sem) { + if (port_interruptNesting[xPortGetCoreID()] != 0) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->sem, &xHigherPriorityTaskWoken); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { + xSemaphoreGive( mutex->sem ); + } + } +} + +void IRAM_ATTR mtx_lock(struct mtx *mutex) { + if (port_interruptNesting[xPortGetCoreID()] != 0) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreTakeFromISR( mutex->sem, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { + xSemaphoreTake( mutex->sem, portMAX_DELAY ); + } +} + +int mtx_trylock(struct mtx *mutex) { + if (xSemaphoreTake( mutex->sem, 0 ) == pdTRUE) { + return 1; + } else { + return 0; + } +} + +void IRAM_ATTR mtx_unlock(struct mtx *mutex) { + if (port_interruptNesting[xPortGetCoreID()] != 0) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->sem, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { + xSemaphoreGive( mutex->sem ); + } +} + +void mtx_destroy(struct mtx *mutex) { + if (port_interruptNesting[xPortGetCoreID()] != 0) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->sem, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { + xSemaphoreGive( mutex->sem ); + } + + vSemaphoreDelete( mutex->sem ); + + mutex->sem = 0; +} diff --git a/components/spiffs/mutex.h b/components/spiffs/mutex.h new file mode 100644 index 0000000..0871e23 --- /dev/null +++ b/components/spiffs/mutex.h @@ -0,0 +1,53 @@ +/* + * Lua RTOS, mutex api implementation over FreeRTOS + * + * Copyright (C) 2015 - 2017 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * + * Author: Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaim all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + * + * Modified by: LoBo (loboris@gmail.com / https://github.com/loboris) + * + */ + +#ifndef MUTEX_H_H +#define MUTEX_H_H + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define MUTEX_INITIALIZER {.sem = 0} + +struct mtx { + SemaphoreHandle_t sem; +}; + + +void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts); +void mtx_lock(struct mtx *mutex); +int mtx_trylock(struct mtx *mutex); +void mtx_unlock(struct mtx *mutex); +void mtx_destroy(struct mtx *mutex); + +#endif /* MUTEX_H_H */ + diff --git a/components/spiffs/spiffs.h b/components/spiffs/spiffs.h new file mode 100644 index 0000000..d87422d --- /dev/null +++ b/components/spiffs/spiffs.h @@ -0,0 +1,813 @@ +/* + * spiffs.h + * + * Created on: May 26, 2013 + * Author: petera + */ + +#ifndef SPIFFS_H_ +#define SPIFFS_H_ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "spiffs_config.h" + +#define SPIFFS_OK 0 +#define SPIFFS_ERR_NOT_MOUNTED -10000 +#define SPIFFS_ERR_FULL -10001 +#define SPIFFS_ERR_NOT_FOUND -10002 +#define SPIFFS_ERR_END_OF_OBJECT -10003 +#define SPIFFS_ERR_DELETED -10004 +#define SPIFFS_ERR_NOT_FINALIZED -10005 +#define SPIFFS_ERR_NOT_INDEX -10006 +#define SPIFFS_ERR_OUT_OF_FILE_DESCS -10007 +#define SPIFFS_ERR_FILE_CLOSED -10008 +#define SPIFFS_ERR_FILE_DELETED -10009 +#define SPIFFS_ERR_BAD_DESCRIPTOR -10010 +#define SPIFFS_ERR_IS_INDEX -10011 +#define SPIFFS_ERR_IS_FREE -10012 +#define SPIFFS_ERR_INDEX_SPAN_MISMATCH -10013 +#define SPIFFS_ERR_DATA_SPAN_MISMATCH -10014 +#define SPIFFS_ERR_INDEX_REF_FREE -10015 +#define SPIFFS_ERR_INDEX_REF_LU -10016 +#define SPIFFS_ERR_INDEX_REF_INVALID -10017 +#define SPIFFS_ERR_INDEX_FREE -10018 +#define SPIFFS_ERR_INDEX_LU -10019 +#define SPIFFS_ERR_INDEX_INVALID -10020 +#define SPIFFS_ERR_NOT_WRITABLE -10021 +#define SPIFFS_ERR_NOT_READABLE -10022 +#define SPIFFS_ERR_CONFLICTING_NAME -10023 +#define SPIFFS_ERR_NOT_CONFIGURED -10024 + +#define SPIFFS_ERR_NOT_A_FS -10025 +#define SPIFFS_ERR_MOUNTED -10026 +#define SPIFFS_ERR_ERASE_FAIL -10027 +#define SPIFFS_ERR_MAGIC_NOT_POSSIBLE -10028 + +#define SPIFFS_ERR_NO_DELETED_BLOCKS -10029 + +#define SPIFFS_ERR_FILE_EXISTS -10030 + +#define SPIFFS_ERR_NOT_A_FILE -10031 +#define SPIFFS_ERR_RO_NOT_IMPL -10032 +#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033 +#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034 +#define SPIFFS_ERR_PROBE_NOT_A_FS -10035 +#define SPIFFS_ERR_NAME_TOO_LONG -10036 + +#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037 +#define SPIFFS_ERR_IX_MAP_MAPPED -10038 +#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039 + +#define SPIFFS_ERR_INTERNAL -10050 + +#define SPIFFS_ERR_TEST -10100 + + +// spiffs file descriptor index type. must be signed +typedef s16_t spiffs_file; +// spiffs file descriptor flags +typedef u16_t spiffs_flags; +// spiffs file mode +typedef u16_t spiffs_mode; +// object type +typedef u8_t spiffs_obj_type; + +struct spiffs_t; + +#if SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system check callback report operation */ +typedef enum { + SPIFFS_CHECK_LOOKUP = 0, + SPIFFS_CHECK_INDEX, + SPIFFS_CHECK_PAGE +} spiffs_check_type; + +/* file system check callback report type */ +typedef enum { + SPIFFS_CHECK_PROGRESS = 0, + SPIFFS_CHECK_ERROR, + SPIFFS_CHECK_FIX_INDEX, + SPIFFS_CHECK_FIX_LOOKUP, + SPIFFS_CHECK_DELETE_ORPHANED_INDEX, + SPIFFS_CHECK_DELETE_PAGE, + SPIFFS_CHECK_DELETE_BAD_FILE +} spiffs_check_report; + +/* file system check callback function */ +#if SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#else // SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system listener callback operation */ +typedef enum { + /* the file has been created */ + SPIFFS_CB_CREATED = 0, + /* the file has been updated or moved to another page */ + SPIFFS_CB_UPDATED, + /* the file has been deleted */ + SPIFFS_CB_DELETED +} spiffs_fileop_type; + +/* file system listener callback function */ +typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix); + +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) \ + printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) printf(__VA_ARGS__) +#endif + +/* Any write to the filehandle is appended to end of the file */ +#define SPIFFS_APPEND (1<<0) +#define SPIFFS_O_APPEND SPIFFS_APPEND +/* If the opened file exists, it will be truncated to zero length before opened */ +#define SPIFFS_TRUNC (1<<1) +#define SPIFFS_O_TRUNC SPIFFS_TRUNC +/* If the opened file does not exist, it will be created before opened */ +#define SPIFFS_CREAT (1<<2) +#define SPIFFS_O_CREAT SPIFFS_CREAT +/* The opened file may only be read */ +#define SPIFFS_RDONLY (1<<3) +#define SPIFFS_O_RDONLY SPIFFS_RDONLY +/* The opened file may only be written */ +#define SPIFFS_WRONLY (1<<4) +#define SPIFFS_O_WRONLY SPIFFS_WRONLY +/* The opened file may be both read and written */ +#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) +#define SPIFFS_O_RDWR SPIFFS_RDWR +/* Any writes to the filehandle will never be cached but flushed directly */ +#define SPIFFS_DIRECT (1<<5) +#define SPIFFS_O_DIRECT SPIFFS_DIRECT +/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */ +#define SPIFFS_EXCL (1<<6) +#define SPIFFS_O_EXCL SPIFFS_EXCL + +#define SPIFFS_SEEK_SET (0) +#define SPIFFS_SEEK_CUR (1) +#define SPIFFS_SEEK_END (2) + +#define SPIFFS_TYPE_FILE (1) +#define SPIFFS_TYPE_DIR (2) +#define SPIFFS_TYPE_HARD_LINK (3) +#define SPIFFS_TYPE_SOFT_LINK (4) + +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) +#endif + +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) +#endif + +// phys structs + +// spiffs spi configuration struct +typedef struct { + // physical read function + spiffs_read hal_read_f; + // physical write function + spiffs_write hal_write_f; + // physical erase function + spiffs_erase hal_erase_f; +#if SPIFFS_SINGLETON == 0 + // physical size of the spi flash + u32_t phys_size; + // physical offset in spi flash used for spiffs, + // must be on block boundary + u32_t phys_addr; + // physical size when erasing a block + u32_t phys_erase_block; + + // logical size of a block, must be on physical + // block size boundary and must never be less than + // a physical block + u32_t log_block_size; + // logical size of a page, must be at least + // log_block_size / 8 + u32_t log_page_size; + +#endif +#if SPIFFS_FILEHDL_OFFSET + // an integer offset added to each file handle + u16_t fh_ix_offset; +#endif +} spiffs_config; + +typedef struct spiffs_t { + // file system configuration + spiffs_config cfg; + // number of logical blocks + u32_t block_count; + + // cursor for free blocks, block index + spiffs_block_ix free_cursor_block_ix; + // cursor for free blocks, entry index + int free_cursor_obj_lu_entry; + // cursor when searching, block index + spiffs_block_ix cursor_block_ix; + // cursor when searching, entry index + int cursor_obj_lu_entry; + + // primary work buffer, size of a logical page + u8_t *lu_work; + // secondary work buffer, size of a logical page + u8_t *work; + // file descriptor memory area + u8_t *fd_space; + // available file descriptors + u32_t fd_count; + + // last error + s32_t err_code; + + // current number of free blocks + u32_t free_blocks; + // current number of busy pages + u32_t stats_p_allocated; + // current number of deleted pages + u32_t stats_p_deleted; + // flag indicating that garbage collector is cleaning + u8_t cleaning; + // max erase count amongst all blocks + spiffs_obj_id max_erase_count; + +#if SPIFFS_GC_STATS + u32_t stats_gc_runs; +#endif + +#if SPIFFS_CACHE + // cache memory + void *cache; + // cache size + u32_t cache_size; +#if SPIFFS_CACHE_STATS + u32_t cache_hits; + u32_t cache_misses; +#endif +#endif + + // check callback function + spiffs_check_callback check_cb_f; + // file callback function + spiffs_file_callback file_cb_f; + // mounted flag + u8_t mounted; + // user data + void *user_data; + // config magic + u32_t config_magic; +} spiffs; + +/* spiffs file status struct */ +typedef struct { + spiffs_obj_id obj_id; + u32_t size; + spiffs_obj_type type; + spiffs_page_ix pix; + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_stat; + +struct spiffs_dirent { + spiffs_obj_id obj_id; + u8_t name[SPIFFS_OBJ_NAME_LEN]; + spiffs_obj_type type; + u32_t size; + spiffs_page_ix pix; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +}; + +typedef struct { + spiffs *fs; + spiffs_block_ix block; + int entry; +} spiffs_DIR; + +#if SPIFFS_IX_MAP + +typedef struct { + // buffer with looked up data pixes + spiffs_page_ix *map_buf; + // precise file byte offset + u32_t offset; + // start data span index of lookup buffer + spiffs_span_ix start_spix; + // end data span index of lookup buffer + spiffs_span_ix end_spix; +} spiffs_ix_map; + +#endif + +// functions + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +/** + * Special function. This takes a spiffs config struct and returns the number + * of blocks this file system was formatted with. This function relies on + * that following info is set correctly in given config struct: + * + * phys_addr, log_page_size, and log_block_size. + * + * Also, hal_read_f must be set in the config struct. + * + * One must be sure of the correct page size and that the physical address is + * correct in the probed file system when calling this function. It is not + * checked if the phys_addr actually points to the start of the file system, + * so one might get a false positive if entering a phys_addr somewhere in the + * middle of the file system at block boundary. In addition, it is not checked + * if the page size is actually correct. If it is not, weird file system sizes + * will be returned. + * + * If this function detects a file system it returns the assumed file system + * size, which can be used to set the phys_size. + * + * Otherwise, it returns an error indicating why it is not regarded as a file + * system. + * + * Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK + * macros. It returns the error code directly, instead of as read by + * SPIFFS_errno. + * + * @param config essential parts of the physical and logical + * configuration of the file system. + */ +s32_t SPIFFS_probe_fs(spiffs_config *config); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +/** + * Initializes the file system dynamic parameters and mounts the filesystem. + * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS + * if the flash does not contain a recognizable file system. + * In this case, SPIFFS_format must be called prior to remounting. + * @param fs the file system struct + * @param config the physical and logical configuration of the file system + * @param work a memory work buffer comprising 2*config->log_page_size + * bytes used throughout all file system operations + * @param fd_space memory for file descriptors + * @param fd_space_size memory size of file descriptors + * @param cache memory for cache, may be null + * @param cache_size memory size of cache + * @param check_cb_f callback function for reporting during consistency checks + */ +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f); + +/** + * Unmounts the file system. All file handles will be flushed of any + * cached writes and closed. + * @param fs the file system struct + */ +void SPIFFS_unmount(spiffs *fs); + +/** + * Creates a new file. + * @param fs the file system struct + * @param path the path of the new file + * @param mode ignored, for posix compliance + */ +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode); + +/** + * Opens/creates a file. + * @param fs the file system struct + * @param path the path of the new file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY, + * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given dir entry. + * Optimization purposes, when traversing a file system with SPIFFS_readdir + * a normal SPIFFS_open would need to traverse the filesystem again to find + * the file, whilst SPIFFS_open_by_dirent already knows where the file resides. + * @param fs the file system struct + * @param e the dir entry to the file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given page index. + * Optimization purposes, opens a file by directly pointing to the page + * index in the spi flash. + * If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE + * is returned. + * @param fs the file system struct + * @param page_ix the page index + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode); + +/** + * Reads from given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf where to put read data + * @param len how much to read + * @returns number of bytes read, or -1 if error + */ +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Writes to given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf the data to write + * @param len how much to write + * @returns number of bytes written, or -1 if error + */ +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Moves the read/write file offset. Resulting offset is returned or negative if error. + * lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset. + * @param fs the file system struct + * @param fh the filehandle + * @param offs how much/where to move the offset + * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes + * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset + * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative + */ +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); + +/** + * Removes a file by path + * @param fs the file system struct + * @param path the path of the file to remove + */ +s32_t SPIFFS_remove(spiffs *fs, const char *path); + +/** + * Removes a file by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to remove + */ +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh); + +/** + * Gets file status by path + * @param fs the file system struct + * @param path the path of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s); + +/** + * Gets file status by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s); + +/** + * Flushes all pending write operations from cache for given file + * @param fs the file system struct + * @param fh the filehandle of the file to flush + */ +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh); + +/** + * Closes a filehandle. If there are pending write operations, these are finalized before closing. + * @param fs the file system struct + * @param fh the filehandle of the file to close + */ +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh); + +/** + * Renames a file + * @param fs the file system struct + * @param old path of file to rename + * @param newPath new path of file + */ +s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath); + +#if SPIFFS_OBJ_META_LEN +/** + * Updates file's metadata + * @param fs the file system struct + * @param path path to the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta); + +/** + * Updates file's metadata + * @param fs the file system struct + * @param fh file handle of the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta); +#endif + +/** + * Returns last error of last file operation. + * @param fs the file system struct + */ +s32_t SPIFFS_errno(spiffs *fs); + +/** + * Clears last error. + * @param fs the file system struct + */ +void SPIFFS_clearerr(spiffs *fs); + +/** + * Opens a directory stream corresponding to the given name. + * The stream is positioned at the first entry in the directory. + * On hydrogen builds the name argument is ignored as hydrogen builds always correspond + * to a flat file structure - no directories. + * @param fs the file system struct + * @param name the name of the directory + * @param d pointer the directory stream to be populated + */ +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d); + +/** + * Closes a directory stream + * @param d the directory stream to close + */ +s32_t SPIFFS_closedir(spiffs_DIR *d); + +/** + * Reads a directory into given spifs_dirent struct. + * @param d pointer to the directory stream + * @param e the dirent struct to be populated + * @returns null if error or end of stream, else given dirent is returned + */ +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e); + +/** + * Runs a consistency check on given filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_check(spiffs *fs); + +/** + * Returns number of total bytes available and number of used bytes. + * This is an estimation, and depends on if there a many files with little + * data or few files with much data. + * NB: If used number of bytes exceeds total bytes, a SPIFFS_check should + * run. This indicates a power loss in midst of things. In worst case + * (repeated powerlosses in mending or gc) you might have to delete some files. + * + * @param fs the file system struct + * @param total total number of bytes in filesystem + * @param used used number of bytes in filesystem + */ +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used); + +/** + * Formats the entire file system. All data will be lost. + * The filesystem must not be mounted when calling this. + * + * NB: formatting is awkward. Due to backwards compatibility, SPIFFS_mount + * MUST be called prior to formatting in order to configure the filesystem. + * If SPIFFS_mount succeeds, SPIFFS_unmount must be called before calling + * SPIFFS_format. + * If SPIFFS_mount fails, SPIFFS_format can be called directly without calling + * SPIFFS_unmount first. + * + * @param fs the file system struct + */ +s32_t SPIFFS_format(spiffs *fs); + +/** + * Returns nonzero if spiffs is mounted, or zero if unmounted. + * @param fs the file system struct + */ +u8_t SPIFFS_mounted(spiffs *fs); + +/** + * Tries to find a block where most or all pages are deleted, and erase that + * block if found. Does not care for wear levelling. Will not move pages + * around. + * If parameter max_free_pages are set to 0, only blocks with only deleted + * pages will be selected. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * Setting max_free_pages to anything larger than zero will eventually wear + * flash more as a block containing free pages can be erased. + * + * Will set err_no to SPIFFS_OK if a block was found and erased, + * SPIFFS_ERR_NO_DELETED_BLOCK if no matching block was found, + * or other error. + * + * @param fs the file system struct + * @param max_free_pages maximum number allowed free pages in block + */ +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages); + +/** + * Will try to make room for given amount of bytes in the filesystem by moving + * pages and erasing blocks. + * If it is physically impossible, err_no will be set to SPIFFS_ERR_FULL. If + * there already is this amount (or more) of free space, SPIFFS_gc will + * silently return. It is recommended to call SPIFFS_info before invoking + * this method in order to determine what amount of bytes to give. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * @param fs the file system struct + * @param size amount of bytes that should be freed + */ +s32_t SPIFFS_gc(spiffs *fs, u32_t size); + +/** + * Check if EOF reached. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh); + +/** + * Get position in file. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh); + +/** + * Registers a callback function that keeps track on operations on file + * headers. Do note, that this callback is called from within internal spiffs + * mechanisms. Any operations on the actual file system being callbacked from + * in this callback will mess things up for sure - do not do this. + * This can be used to track where files are and move around during garbage + * collection, which in turn can be used to build location tables in ram. + * Used in conjuction with SPIFFS_open_by_page this may improve performance + * when opening a lot of files. + * Must be invoked after mount. + * + * @param fs the file system struct + * @param cb_func the callback on file operations + */ +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func); + +#if SPIFFS_IX_MAP + +/** + * Maps the first level index lookup to a given memory map. + * This will make reading big files faster, as the memory map will be used for + * looking up data pages instead of searching for the indices on the physical + * medium. When mapping, all affected indicies are found and the information is + * copied to the array. + * Whole file or only parts of it may be mapped. The index map will cover file + * contents from argument offset until and including arguments (offset+len). + * It is valid to map a longer range than the current file size. The map will + * then be populated when the file grows. + * On garbage collections and file data page movements, the map array will be + * automatically updated. Do not tamper with the map array, as this contains + * the references to the data pages. Modifying it from outside will corrupt any + * future readings using this file descriptor. + * The map will no longer be used when the file descriptor closed or the file + * is unmapped. + * This can be useful to get faster and more deterministic timing when reading + * large files, or when seeking and reading a lot within a file. + * @param fs the file system struct + * @param fh the file handle of the file to map + * @param map a spiffs_ix_map struct, describing the index map + * @param offset absolute file offset where to start the index map + * @param len length of the mapping in actual file bytes + * @param map_buf the array buffer for the look up data - number of required + * elements in the array can be derived from function + * SPIFFS_bytes_to_ix_map_entries given the length + */ +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf); + +/** + * Unmaps the index lookup from this filehandle. All future readings will + * proceed as normal, requiring reading of the first level indices from + * physical media. + * The map and map buffer given in function SPIFFS_ix_map will no longer be + * referenced by spiffs. + * It is not strictly necessary to unmap a file before closing it, as closing + * a file will automatically unmap it. + * @param fs the file system struct + * @param fh the file handle of the file to unmap + */ +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh); + +/** + * Moves the offset for the index map given in function SPIFFS_ix_map. Parts or + * all of the map buffer will repopulated. + * @param fs the file system struct + * @param fh the mapped file handle of the file to remap + * @param offset new absolute file offset where to start the index map + */ +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs); + +/** + * Utility function to get number of spiffs_page_ix entries a map buffer must + * contain on order to map given amount of file data in bytes. + * See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes. + * @param fs the file system struct + * @param bytes number of file data bytes to map + * @return needed number of elements in a spiffs_page_ix array needed to + * map given amount of bytes in a file + */ +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes); + +/** + * Utility function to amount of file data bytes that can be mapped when + * mapping a file with buffer having given number of spiffs_page_ix entries. + * See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries. + * @param fs the file system struct + * @param map_page_ix_entries number of entries in a spiffs_page_ix array + * @return amount of file data in bytes that can be mapped given a map + * buffer having given amount of spiffs_page_ix entries + */ +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries); + +#endif // SPIFFS_IX_MAP + + +#if SPIFFS_TEST_VISUALISATION +/** + * Prints out a visualization of the filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_vis(spiffs *fs); +#endif + +#if SPIFFS_BUFFER_HELP +/** + * Returns number of bytes needed for the filedescriptor buffer given + * amount of file descriptors. + */ +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs); + +#if SPIFFS_CACHE +/** + * Returns number of bytes needed for the cache buffer given + * amount of cache pages. + */ +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages); +#endif +#endif + +#if SPIFFS_CACHE +#endif +#if defined(__cplusplus) +} +#endif + +#endif /* SPIFFS_H_ */ diff --git a/components/spiffs/spiffs_cache.c b/components/spiffs/spiffs_cache.c new file mode 100644 index 0000000..018f763 --- /dev/null +++ b/components/spiffs/spiffs_cache.c @@ -0,0 +1,314 @@ +/* + * spiffs_cache.c + * + * Created on: Jun 23, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_CACHE + +// returns cached page for give page index, or null if no such cached page +static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache *cache = spiffs_get_cache(fs); + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0; + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + cp->pix == pix ) { + SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix); + cp->last_access = cache->last_access; + return cp; + } + } + //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix); + return 0; +} + +// frees cached page +static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, ix); + if (cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { + u8_t *mem = spiffs_get_cache_page(fs, cache, ix); + res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); + } + + cp->flags = 0; + cache->cpage_use_map &= ~(1 << ix); + + if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id); + } else { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix); + } + } + + return res; +} + +// removes the oldest accessed cached page +static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) { + // at least one free cpage + return SPIFFS_OK; + } + + // all busy, scan thru all to find the cpage which has oldest access + int i; + int cand_ix = -1; + u32_t oldest_val = 0; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->last_access - cp->last_access) > oldest_val && + (cp->flags & flag_mask) == flags) { + oldest_val = cache->last_access - cp->last_access; + cand_ix = i; + } + } + + if (cand_ix >= 0) { + res = spiffs_cache_page_free(fs, cand_ix, 1); + } + + return res; +} + +// allocates a new cached page and returns it, or null if all cache pages are busy +static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) { + spiffs_cache *cache = spiffs_get_cache(fs); + if (cache->cpage_use_map == 0xffffffff) { + // out of cache memory + return 0; + } + int i; + for (i = 0; i < cache->cpage_count; i++) { + if ((cache->cpage_use_map & (1<cpage_use_map |= (1<last_access = cache->last_access; + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i); + return cp; + } + } + // out of cache entries + return 0; +} + +// drops the cache page for give page index +void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + if (cp) { + spiffs_cache_page_free(fs, cp->ix, 0); + } +} + +// ------------------------------ + +// reads from spi flash or the cache +s32_t spiffs_phys_rd( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *dst) { + (void)fh; + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); + cache->last_access++; + if (cp) { + // we've already got one, you see +#if SPIFFS_CACHE_STATS + fs->cache_hits++; +#endif + cp->last_access = cache->last_access; + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { + // for second layer lookup functions, we do not cache in order to prevent shredding + return SPIFFS_HAL_READ(fs, addr, len, dst); + } +#if SPIFFS_CACHE_STATS + fs->cache_misses++; +#endif + // this operation will always free one cache page (unless all already free), + // the result code stems from the write operation of the possibly freed cache page + res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + + cp = spiffs_cache_page_allocate(fs); + if (cp) { + cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; + cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + + s32_t res2 = SPIFFS_HAL_READ(fs, + addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + spiffs_get_cache_page(fs, cache, cp->ix)); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + // this will never happen, last resort for sake of symmetry + s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + } + } + return res; +} + +// writes to spi flash and/or the cache +s32_t spiffs_phys_wr( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *src) { + (void)fh; + spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + + if (cp && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) { + // have a cache page + // copy in data to cache page + + if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE && + (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { + // page is being deleted, wipe from cache - unless it is a lookup page + spiffs_cache_page_free(fs, cp->ix, 0); + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } + + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); + + cache->last_access++; + cp->last_access = cache->last_access; + + if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { + // page is being updated, no write-cache, just pass thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } else { + return SPIFFS_OK; + } + } else { + // no cache page, no write cache - just write thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } +} + +#if SPIFFS_CACHE_WR +// returns the cache page that this fd refers, or null if no cache page +spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) { + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { + // all cpages free, no cpage cannot be assigned to obj_id + return 0; + } + + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) && + cp->obj_id == fd->obj_id) { + return cp; + } + } + + return 0; +} + +// allocates a new cache page and refers this to given fd - flushes an old cache +// page if all cache is busy +spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) { + // before this function is called, it is ensured that there is no already existing + // cache page with same object id + spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + spiffs_cache_page *cp = spiffs_cache_page_allocate(fs); + if (cp == 0) { + // could not get cache page + return 0; + } + + cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; + cp->obj_id = fd->obj_id; + fd->cache_page = cp; + return cp; +} + +// unrefers all fds that this cache page refers to and releases the cache page +void spiffs_cache_fd_release(spiffs *fs, spiffs_cache_page *cp) { + if (cp == 0) return; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0 && cur_fd->cache_page == cp) { + cur_fd->cache_page = 0; + } + } + spiffs_cache_page_free(fs, cp->ix, 0); + + cp->obj_id = 0; +} + +#endif + +// initializes the cache +void spiffs_cache_init(spiffs *fs) { + if (fs->cache == 0) return; + u32_t sz = fs->cache_size; + u32_t cache_mask = 0; + int i; + int cache_entries = + (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs)); + if (cache_entries <= 0) return; + + for (i = 0; i < cache_entries; i++) { + cache_mask <<= 1; + cache_mask |= 1; + } + + spiffs_cache cache; + memset(&cache, 0, sizeof(spiffs_cache)); + cache.cpage_count = cache_entries; + cache.cpages = (u8_t *)((u8_t *)fs->cache + sizeof(spiffs_cache)); + + cache.cpage_use_map = 0xffffffff; + cache.cpage_use_mask = cache_mask; + memcpy(fs->cache, &cache, sizeof(spiffs_cache)); + + spiffs_cache *c = spiffs_get_cache(fs); + + memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs)); + + c->cpage_use_map &= ~(c->cpage_use_mask); + for (i = 0; i < cache.cpage_count; i++) { + spiffs_get_cache_page_hdr(fs, c, i)->ix = i; + } +} + +#endif // SPIFFS_CACHE diff --git a/components/spiffs/spiffs_check.c b/components/spiffs/spiffs_check.c new file mode 100644 index 0000000..dde85ef --- /dev/null +++ b/components/spiffs/spiffs_check.c @@ -0,0 +1,995 @@ +/* + * spiffs_check.c + * + * Contains functionality for checking file system consistency + * and mending problems. + * Three levels of consistency checks are implemented: + * + * Look up consistency + * Checks if indices in lookup pages are coherent with page headers + * Object index consistency + * Checks if there are any orphaned object indices (missing object index headers). + * If an object index is found but not its header, the object index is deleted. + * This is critical for the following page consistency check. + * Page consistency + * Checks for pages that ought to be indexed, ought not to be indexed, are multiple indexed + * + * + * Created on: Jul 7, 2013 + * Author: petera + */ + + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +#if SPIFFS_HAL_CALLBACK_EXTRA +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#else +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#endif + +//--------------------------------------- +// Look up consistency + +// searches in the object indices and returns the referenced page index given +// the object id and the data span index +// destroys fs->lu_work +static s32_t spiffs_object_get_data_page_index_reference( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix data_spix, + spiffs_page_ix *pix, + spiffs_page_ix *objix_pix) { + s32_t res; + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // find obj index for obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, objix_pix); + SPIFFS_CHECK_RES(res); + + // load obj index entry + u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, *objix_pix); + if (objix_spix == 0) { + // get referenced page from object index header + addr += sizeof(spiffs_page_object_ix_header) + data_spix * sizeof(spiffs_page_ix); + } else { + // get referenced page from object index + addr += sizeof(spiffs_page_object_ix) + SPIFFS_OBJ_IX_ENTRY(fs, data_spix) * sizeof(spiffs_page_ix); + } + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, addr, sizeof(spiffs_page_ix), (u8_t *)pix); + + return res; +} + +// copies page contents to a new page +static s32_t spiffs_rewrite_page(spiffs *fs, spiffs_page_ix cur_pix, spiffs_page_header *p_hdr, spiffs_page_ix *new_pix) { + s32_t res; + res = spiffs_page_allocate_data(fs, p_hdr->obj_id, p_hdr, 0,0,0,0, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, *new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + return res; +} + +// rewrites the object index for given object id and replaces the +// data page index to a new page index +static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ix data_spix, spiffs_page_ix new_data_pix, spiffs_page_ix objix_pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (objix_spix == 0) { + // calc index in index header + entry = data_spix; + } else { + // calc entry in index + entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix); + + } + // load index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + // be ultra safe, double check header against provided data + if (objix_p_hdr->obj_id != obj_id) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_OBJ_ID_MISM; + } + if (objix_p_hdr->span_ix != objix_spix) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_SPIX_MISM; + } + if ((objix_p_hdr->flags & (SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_INDEX | + SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) != + (SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_DELET)) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_FLAGS_BAD; + } + + // rewrite in mem + if (objix_spix == 0) { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + } else { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + res = spiffs_page_delete(fs, objix_pix); + + return res; +} + +// deletes an object just by marking object index header as deleted +static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) { + spiffs_page_ix objix_hdr_pix; + s32_t res; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id, 0, 0, &objix_hdr_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + return SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_IXDELE; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + return res; +} + +// validates the given look up entry +static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, spiffs_page_header *p_hdr, + spiffs_page_ix cur_pix, spiffs_block_ix cur_block, int cur_entry, int *reload_lu) { + (void)cur_block; + (void)cur_entry; + u8_t delete_page = 0; + s32_t res = SPIFFS_OK; + spiffs_page_ix objix_pix; + spiffs_page_ix ref_pix; + // check validity, take actions + if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) || + ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) { + // look up entry deleted / free but used in page header + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix); + *reload_lu = 1; + delete_page = 1; + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // header says data page + // data page can be removed if not referenced by some object index + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix == cur_pix) { + // data page referenced by object index but deleted in lu + // copy page to new place and re-write the object index to new place + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } else { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // header says index page + // index page can be removed if other index with same obj_id and spanix is found + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, 0); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no such index page found, check for a data page amongst page headers + // lu cannot be trusted + res = spiffs_obj_lu_find_id_and_span_by_phdr(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, 0); + if (res == SPIFFS_OK) { // ignore other errors + // got a data page also, assume lu corruption only, rewrite to new page + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + } + } else { + SPIFFS_CHECK_RES(res); + } + } + } + if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) { + // look up entry used + if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id); + delete_page = 1; + if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 || + (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) || + (p_hdr->flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE)) == 0) { + // page deleted or not finalized, just remove it + } else { + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // if data page, check for reference to this page + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + // if found, rewrite page with object id, update index, and delete current + if (ref_pix == cur_pix) { + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // else if index, check for other pages with both obj_id's and spanix + spiffs_page_ix objix_pix_lu, objix_pix_ph; + // see if other object index page exists for lookup obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + // if both obj_id's found, just delete current + if (objix_pix_ph == 0 || objix_pix_lu == 0) { + // otherwise try finding first corresponding data pages + spiffs_page_ix data_pix_lu, data_pix_ph; + // see if other data page exists for look up obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other data page exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL); + new_ph.span_ix = p_hdr->span_ix; + spiffs_page_ix new_pix; + if ((objix_pix_lu && data_pix_lu && data_pix_ph && objix_pix_ph == 0) || + (objix_pix_lu == 0 && data_pix_ph && objix_pix_ph == 0)) { + // got a data page for page header obj id + // rewrite as obj_id_ph + new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) || + (objix_pix_ph == 0 && data_pix_lu && objix_pix_lu == 0)) { + // got a data page for look up obj id + // rewrite as obj_id_lu + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else { + // cannot safely do anything + SPIFFS_CHECK_DBG("LU: FIXUP: nothing to do, just delete\n"); + } + } + } + } + } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) || + ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) { + SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix); + spiffs_page_ix data_pix, objix_pix_d; + // see if other data page exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &objix_pix_d); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_d = 0; + } + SPIFFS_CHECK_RES(res); + + delete_page = 1; + // if other data page exists and object index exists, just delete page + if (data_pix && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index and data page exists, simply remove\n"); + } else + // if only data page exists, make this page index + if (data_pix && objix_pix_d == 0) { + SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else + // if only index exists, make data page + if (data_pix == 0 && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else { + // if nothing exists, we cannot safely make a decision - delete + } + } + else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix); + delete_page = 1; + } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix); + // page can be removed if not referenced by object index + *reload_lu = 1; + res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + delete_page = 1; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix != cur_pix) { + SPIFFS_CHECK_DBG("LU: FIXUP: other finalized page is referred, just delete\n"); + delete_page = 1; + } else { + // page referenced by object index but not final + // just finalize + SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), (u8_t*)&flags); + } + } + } + } + + if (delete_page) { + SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + + return res; +} + +static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry, + const void *user_const_p, void *user_var_p) { + (void)user_const_p; + (void)user_var_p; + s32_t res = SPIFFS_OK; + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + int reload_lu = 0; + + res = spiffs_lookup_check_validate(fs, obj_id, &p_hdr, cur_pix, cur_block, cur_entry, &reload_lu); + SPIFFS_CHECK_RES(res); + + if (res == SPIFFS_OK) { + return reload_lu ? SPIFFS_VIS_COUNTINUE_RELOAD : SPIFFS_VIS_COUNTINUE; + } + return res; +} + + +// Scans all object look up. For each entry, corresponding page header is checked for validity. +// If an object index header page is found, this is also checked +s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) { + (void)check_all_objects; + s32_t res = SPIFFS_OK; + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); + + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); + } + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); + + return res; +} + +//--------------------------------------- +// Page consistency + +// Scans all pages (except lu pages), reserves 4 bits in working memory for each page +// bit 0: 0 == FREE|DELETED, 1 == USED +// bit 1: 0 == UNREFERENCED, 1 == REFERENCED +// bit 2: 0 == NOT_INDEX, 1 == INDEX +// bit 3: unused +// A consistent file system will have only pages being +// * x000 free, unreferenced, not index +// * x011 used, referenced only once, not index +// * x101 used, unreferenced, index +// The working memory might not fit all pages so several scans might be needed +static s32_t spiffs_page_consistency_check_i(spiffs *fs) { + const u32_t bits = 4; + const spiffs_page_ix pages_per_scan = SPIFFS_CFG_LOG_PAGE_SZ(fs) * 8 / bits; + + s32_t res = SPIFFS_OK; + spiffs_page_ix pix_offset = 0; + + // for each range of pages fitting into work memory + while (pix_offset < SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) { + // set this flag to abort all checks and rescan the page range + u8_t restart = 0; + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + spiffs_block_ix cur_block = 0; + // build consistency bitmap for id range traversing all blocks + while (!restart && cur_block < fs->block_count) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, + (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) + + ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count), + 0); + // traverse each page except for lookup pages + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; + while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { + //if ((cur_pix & 0xff) == 0) + // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n", + // cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count); + + // read header + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + u8_t within_range = (cur_pix >= pix_offset && cur_pix < pix_offset + pages_per_scan); + const u32_t pix_byte_ix = (cur_pix - pix_offset) / (8/bits); + const u8_t pix_bit_ix = (cur_pix & ((8/bits)-1)) * bits; + + if (within_range && + (p_hdr.flags & SPIFFS_PH_FLAG_DELET) && (p_hdr.flags & SPIFFS_PH_FLAG_USED) == 0) { + // used + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 0)); + } + if ((p_hdr.flags & SPIFFS_PH_FLAG_DELET) && + (p_hdr.flags & SPIFFS_PH_FLAG_IXDELE) && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) == 0) { + // found non-deleted index + if (within_range) { + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 2)); + } + + // load non-deleted index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + + // traverse index for referenced pages + spiffs_page_ix *object_page_index; + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + int entries; + int i; + spiffs_span_ix data_spix_offset; + if (p_hdr.span_ix == 0) { + // object header page index + entries = SPIFFS_OBJ_HDR_IX_LEN(fs); + data_spix_offset = 0; + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)); + } else { + // object page index + entries = SPIFFS_OBJ_IX_LEN(fs); + data_spix_offset = SPIFFS_OBJ_HDR_IX_LEN(fs) + SPIFFS_OBJ_IX_LEN(fs) * (p_hdr.span_ix - 1); + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)); + } + + // for all entries in index + for (i = 0; !restart && i < entries; i++) { + spiffs_page_ix rpix = object_page_index[i]; + u8_t rpix_within_range = rpix >= pix_offset && rpix < pix_offset + pages_per_scan; + + if ((rpix != (spiffs_page_ix)-1 && rpix > SPIFFS_MAX_PAGES(fs)) + || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) { + + // bad reference + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // check for data page elsewhere + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, 0, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // if not, allocate free page + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = data_spix_offset + i; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix); + SPIFFS_CHECK_RES(res); + SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix); + } + // remap index + SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix); + res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); + // delete file + res = spiffs_page_delete(fs, cur_pix); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + + } else if (rpix_within_range) { + + // valid reference + // read referenced page header + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + + // cross reference page header check + if (rp_hdr.obj_id != (p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) || + rp_hdr.span_ix != data_spix_offset + i || + (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) != + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n", + rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, + rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags); + // try finding correct page + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, rpix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // not found, this index is badly borked + SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + break; + } else { + // found it, so rewrite index + SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n", + data_pix, cur_pix, p_hdr.obj_id); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + } + } + else { + // mark rpix as referenced + const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits); + const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits; + if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // Here, we should have fixed all broken references - getting this means there + // must be multiple files with same object id. Only solution is to delete + // the object which is referring to this page + SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n", + p_hdr.obj_id, cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + // extra precaution, delete this page also + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + restart = 1; + } + fs->work[rpix_byte_ix] |= (1<<(rpix_bit_ix + 1)); + } + } + } // for all index entries + } // found index + + // next page + cur_pix++; + } + // next block + cur_block++; + } + // check consistency bitmap + if (!restart) { + spiffs_page_ix objix_pix; + spiffs_page_ix rpix; + + u32_t byte_ix; + u8_t bit_ix; + for (byte_ix = 0; !restart && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs); byte_ix++) { + for (bit_ix = 0; !restart && bit_ix < 8/bits; bit_ix ++) { + u8_t bitmask = (fs->work[byte_ix] >> (bit_ix * bits)) & 0x7; + spiffs_page_ix cur_pix = pix_offset + byte_ix * (8/bits) + bit_ix; + + // 000 ok - free, unreferenced, not index + + if (bitmask == 0x1) { + + // 001 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix); + + u8_t rewrite_ix_to_this = 0; + u8_t delete_page = 0; + // check corresponding object index entry + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_get_data_page_index_reference(fs, p_hdr.obj_id, p_hdr.span_ix, + &rpix, &objix_pix); + if (res == SPIFFS_OK) { + if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) { + // pointing to a bad page altogether, rewrite index to this + rewrite_ix_to_this = 1; + SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix); + } else { + // pointing to something else, check what + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + if (((p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) == rp_hdr.obj_id) && + ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) == + (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) { + // pointing to something else valid, just delete this page then + SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix); + delete_page = 1; + } else { + // pointing to something weird, update index to point to this page instead + if (rpix != cur_pix) { + SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix, + (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ", + (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ", + (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "", + (rp_hdr.flags & SPIFFS_PH_FLAG_FINAL) ? "NOTFINAL " : "", + cur_pix); + rewrite_ix_to_this = 1; + } else { + // should not happen, destined for fubar + } + } + } + } else if (res == SPIFFS_ERR_NOT_FOUND) { + SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix); + delete_page = 1; + res = SPIFFS_OK; + } + + if (rewrite_ix_to_this) { + // if pointing to invalid page, redirect index to this page + SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n", + p_hdr.obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + continue; + } else if (delete_page) { + SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + } + SPIFFS_CHECK_RES(res); + } + if (bitmask == 0x2) { + + // 010 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + + // 011 ok - busy, referenced, not index + + if (bitmask == 0x4) { + + // 100 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix); + + // this should never happen, major fubar + } + + // 101 ok - busy, unreferenced, index + + if (bitmask == 0x6) { + + // 110 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + if (bitmask == 0x7) { + + // 111 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + } + } + } + + SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart); + // next page range + if (!restart) { + pix_offset += pages_per_scan; + } + } // while page range not reached end + return res; +} + +// Checks consistency amongst all pages and fixes irregularities +s32_t spiffs_page_consistency_check(spiffs *fs) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); + s32_t res = spiffs_page_consistency_check_i(fs); + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +//--------------------------------------- +// Object index consistency + +// searches for given object id in temporary object id index, +// returns the index or -1 +static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) { + u32_t i; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id); i++) { + if ((obj_table[i] & ~SPIFFS_OBJ_ID_IX_FLAG) == obj_id) { + return i; + } + } + return -1; +} + +static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, + int cur_entry, const void *user_const_p, void *user_var_p) { + (void)user_const_p; + s32_t res_c = SPIFFS_VIS_COUNTINUE; + s32_t res = SPIFFS_OK; + u32_t *log_ix = (u32_t*)user_var_p; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + if (p_hdr.span_ix == 0 && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET)) { + SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + return res_c; + } + + if ((p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + return res_c; + } + + if (p_hdr.span_ix == 0) { + // objix header page, register objid as reachable + int r = spiffs_object_index_search(fs, obj_id); + if (r == -1) { + // not registered, do it + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } + } else { // span index + // objix page, see if header can be found + int r = spiffs_object_index_search(fs, obj_id); + u8_t delete = 0; + if (r == -1) { + // not in temporary index, try finding it + spiffs_page_ix objix_hdr_pix; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &objix_hdr_pix); + res_c = SPIFFS_VIS_COUNTINUE_RELOAD; + if (res == SPIFFS_OK) { + // found, register as reachable + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + } else if (res == SPIFFS_ERR_NOT_FOUND) { + // not found, register as unreachable + delete = 1; + obj_table[*log_ix] = obj_id | SPIFFS_OBJ_ID_IX_FLAG; + } else { + SPIFFS_CHECK_RES(res); + } + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } else { + // in temporary index, check reachable flag + if ((obj_table[r] & SPIFFS_OBJ_ID_IX_FLAG)) { + // registered as unreachable + delete = 1; + } + } + + if (delete) { + SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + } // span index + } // valid object index id + + return res_c; +} + +// Removes orphaned and partially deleted index pages. +// Scans for index pages. When an index page is found, corresponding index header is searched for. +// If no such page exists, the index page cannot be reached as no index header exists and must be +// deleted. +s32_t spiffs_object_index_consistency_check(spiffs *fs) { + s32_t res = SPIFFS_OK; + // impl note: + // fs->work is used for a temporary object index memory, listing found object ids and + // indicating whether they can be reached or not. Acting as a fifo if object ids cannot fit. + // In the temporary object index memory, SPIFFS_OBJ_ID_IX_FLAG bit is used to indicate + // a reachable/unreachable object id. + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + u32_t obj_id_log_ix = 0; + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix, + 0, 0); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/components/spiffs/spiffs_config.h b/components/spiffs/spiffs_config.h new file mode 100644 index 0000000..b4d3b9b --- /dev/null +++ b/components/spiffs/spiffs_config.h @@ -0,0 +1,367 @@ +/* + * spiffs_config.h + * + * Created on: Jul 3, 2013 + * Author: petera + */ + +#ifndef SPIFFS_CONFIG_H_ +#define SPIFFS_CONFIG_H_ + +// ----------- 8< ------------ +// Following includes are for the linux test build of spiffs +// These may/should/must be removed/altered/replaced in your target +#include +#include +#include +#include +#include +#include +#include +// ----------- >8 ------------ + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +typedef signed int s32_t; +typedef unsigned int u32_t; +typedef signed short s16_t; +typedef unsigned short u16_t; +typedef signed char s8_t; +typedef unsigned char u8_t; + +QueueHandle_t spiffs_mutex; + +// compile time switches + +// Set generic spiffs debug output call. +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for garbage collecting. +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for caching. +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for system consistency checks. +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__) +#endif + + +// Defines spiffs debug print formatters +// some general signed number +#ifndef _SPIPRIi +#define _SPIPRIi "%d" +#endif +// address +#ifndef _SPIPRIad +#define _SPIPRIad "%08x" +#endif +// block +#ifndef _SPIPRIbl +#define _SPIPRIbl "%04x" +#endif +// page +#ifndef _SPIPRIpg +#define _SPIPRIpg "%04x" +#endif +// span index +#ifndef _SPIPRIsp +#define _SPIPRIsp "%04x" +#endif +// file descriptor +#ifndef _SPIPRIfd +#define _SPIPRIfd "%d" +#endif +// file object id +#ifndef _SPIPRIid +#define _SPIPRIid "%04x" +#endif +// file flags +#ifndef _SPIPRIfl +#define _SPIPRIfl "%02x" +#endif + +// Enable/disable API functions to determine exact number of bytes +// for filedescriptor and cache buffers. Once decided for a configuration, +// this can be disabled to reduce flash. +#ifndef SPIFFS_BUFFER_HELP +#define SPIFFS_BUFFER_HELP 0 +#endif + +// Enables/disable memory read caching of nucleus file system operations. +// If enabled, memory area must be provided for cache in SPIFFS_mount. +#ifndef SPIFFS_CACHE +#define SPIFFS_CACHE 1 +#endif +#if SPIFFS_CACHE +// Enables memory write caching for file descriptors in hydrogen +#ifndef SPIFFS_CACHE_WR +#define SPIFFS_CACHE_WR 1 +#endif + +// Enable/disable statistics on caching. Debug/test purpose only. +#ifndef SPIFFS_CACHE_STATS +#define SPIFFS_CACHE_STATS 0 +#endif +#endif + +// Always check header of each accessed page to ensure consistent state. +// If enabled it will increase number of reads, will increase flash. +#ifndef SPIFFS_PAGE_CHECK +#define SPIFFS_PAGE_CHECK 0 +#endif + +// Define maximum number of gc runs to perform to reach desired free pages. +#ifndef SPIFFS_GC_MAX_RUNS +#define SPIFFS_GC_MAX_RUNS 5 +#endif + +// Enable/disable statistics on gc. Debug/test purpose only. +#ifndef SPIFFS_GC_STATS +#define SPIFFS_GC_STATS 0 +#endif + +// Garbage collecting examines all pages in a block which and sums up +// to a block score. Deleted pages normally gives positive score and +// used pages normally gives a negative score (as these must be moved). +// To have a fair wear-leveling, the erase age is also included in score, +// whose factor normally is the most positive. +// The larger the score, the more likely it is that the block will +// picked for garbage collection. + +// Garbage collecting heuristics - weight used for deleted pages. +#ifndef SPIFFS_GC_HEUR_W_DELET +#define SPIFFS_GC_HEUR_W_DELET (5) +#endif +// Garbage collecting heuristics - weight used for used pages. +#ifndef SPIFFS_GC_HEUR_W_USED +#define SPIFFS_GC_HEUR_W_USED (-1) +#endif +// Garbage collecting heuristics - weight used for time between +// last erased and erase of this block. +#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE +#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) +#endif + +// Object name maximum length. Note that this length include the +// zero-termination character, meaning maximum string of characters +// can at most be SPIFFS_OBJ_NAME_LEN - 1. +#ifndef SPIFFS_OBJ_NAME_LEN +#define SPIFFS_OBJ_NAME_LEN (64) +#endif + +// Maximum length of the metadata associated with an object. +// Setting to non-zero value enables metadata-related API but also +// changes the on-disk format, so the change is not backward-compatible. +// +// Do note: the meta length must never exceed +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// +// This is derived from following: +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + +// spiffs_object_ix_header fields + at least some LUT entries) +#ifndef SPIFFS_OBJ_META_LEN +#define SPIFFS_OBJ_META_LEN (64) +#endif + +// Size of buffer allocated on stack used when copying data. +// Lower value generates more read/writes. No meaning having it bigger +// than logical page size. +#ifndef SPIFFS_COPY_BUFFER_STACK +#define SPIFFS_COPY_BUFFER_STACK (256) +#endif + +// Enable this to have an identifiable spiffs filesystem. This will look for +// a magic in all sectors to determine if this is a valid spiffs system or +// not on mount point. If not, SPIFFS_format must be called prior to mounting +// again. +#ifndef SPIFFS_USE_MAGIC +#define SPIFFS_USE_MAGIC (1) +#endif + +#if SPIFFS_USE_MAGIC +// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is +// enabled, the magic will also be dependent on the length of the filesystem. +// For example, a filesystem configured and formatted for 4 megabytes will not +// be accepted for mounting with a configuration defining the filesystem as 2 +// megabytes. +#ifndef SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_USE_MAGIC_LENGTH (1) +#endif +#endif + +// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level +// These should be defined on a multithreaded system + +// define this to enter a mutex if you're running on a multithreaded system +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) xSemaphoreTake(spiffs_mutex, portMAX_DELAY) +#endif +// define this to exit a mutex if you're running on a multithreaded system +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) xSemaphoreGive(spiffs_mutex) +#endif + +// Enable if only one spiffs instance with constant configuration will exist +// on the target. This will reduce calculations, flash and memory accesses. +// Parts of configuration must be defined below instead of at time of mount. +#ifndef SPIFFS_SINGLETON +#define SPIFFS_SINGLETON 0 +#endif + +#if SPIFFS_SINGLETON +// Instead of giving parameters in config struct, singleton build must +// give parameters in defines below. +#ifndef SPIFFS_CFG_PHYS_SZ +#define SPIFFS_CFG_PHYS_SZ(ignore) (1024*1024*2) +#endif +#ifndef SPIFFS_CFG_PHYS_ERASE_SZ +#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (65536) +#endif +#ifndef SPIFFS_CFG_PHYS_ADDR +#define SPIFFS_CFG_PHYS_ADDR(ignore) (0) +#endif +#ifndef SPIFFS_CFG_LOG_PAGE_SZ +#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (256) +#endif +#ifndef SPIFFS_CFG_LOG_BLOCK_SZ +#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (65536) +#endif +#endif + +// Enable this if your target needs aligned data for index tables +#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES +#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1 +#endif + +// Enable this if you want the HAL callbacks to be called with the spiffs struct +#ifndef SPIFFS_HAL_CALLBACK_EXTRA +#define SPIFFS_HAL_CALLBACK_EXTRA 0 +#endif + +// Enable this if you want to add an integer offset to all file handles +// (spiffs_file). This is useful if running multiple instances of spiffs on +// same target, in order to recognise to what spiffs instance a file handle +// belongs. +// NB: This adds config field fh_ix_offset in the configuration struct when +// mounting, which must be defined. +#ifndef SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FILEHDL_OFFSET 0 +#endif + +// Enable this to compile a read only version of spiffs. +// This will reduce binary size of spiffs. All code comprising modification +// of the file system will not be compiled. Some config will be ignored. +// HAL functions for erasing and writing to spi-flash may be null. Cache +// can be disabled for even further binary size reduction (and ram savings). +// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. +// If the file system cannot be mounted due to aborted erase operation and +// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be +// returned. +// Might be useful for e.g. bootloaders and such. +#ifndef SPIFFS_READ_ONLY +#define SPIFFS_READ_ONLY 0 +#endif + +// Enable this to add a temporal file cache using the fd buffer. +// The effects of the cache is that SPIFFS_open will find the file faster in +// certain cases. It will make it a lot easier for spiffs to find files +// opened frequently, reducing number of readings from the spi flash for +// finding those files. +// This will grow each fd by 6 bytes. If your files are opened in patterns +// with a degree of temporal locality, the system is optimized. +// Examples can be letting spiffs serve web content, where one file is the css. +// The css is accessed for each html file that is opened, meaning it is +// accessed almost every second time a file is opened. Another example could be +// a log file that is often opened, written, and closed. +// The size of the cache is number of given file descriptors, as it piggybacks +// on the fd update mechanism. The cache lives in the closed file descriptors. +// When closed, the fd know the whereabouts of the file. Instead of forgetting +// this, the temporal cache will keep handling updates to that file even if the +// fd is closed. If the file is opened again, the location of the file is found +// directly. If all available descriptors become opened, all cache memory is +// lost. +#ifndef SPIFFS_TEMPORAL_FD_CACHE +#define SPIFFS_TEMPORAL_FD_CACHE 1 +#endif + +// Temporal file cache hit score. Each time a file is opened, all cached files +// will lose one point. If the opened file is found in cache, that entry will +// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this +// value for the specific access patterns of the application. However, it must +// be between 1 (no gain for hitting a cached entry often) and 255. +#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE +#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 8 +#endif + +// Enable to be able to map object indices to memory. +// This allows for faster and more deterministic reading if cases of reading +// large files and when changing file offset by seeking around a lot. +// When mapping a file's index, the file system will be scanned for index pages +// and the info will be put in memory provided by user. When reading, the +// memory map can be looked up instead of searching for index pages on the +// medium. This way, user can trade memory against performance. +// Whole, parts of, or future parts not being written yet can be mapped. The +// memory array will be owned by spiffs and updated accordingly during garbage +// collecting or when modifying the indices. The latter is invoked by when the +// file is modified in some way. The index buffer is tied to the file +// descriptor. +#ifndef SPIFFS_IX_MAP +#define SPIFFS_IX_MAP 1 +#endif + +// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function +// in the api. This function will visualize all filesystem using given printf +// function. +#ifndef SPIFFS_TEST_VISUALISATION +#define SPIFFS_TEST_VISUALISATION 0 +#endif +#if SPIFFS_TEST_VISUALISATION +#ifndef spiffs_printf +#define spiffs_printf(...) printf(__VA_ARGS__) +#endif +// spiffs_printf argument for a free page +#ifndef SPIFFS_TEST_VIS_FREE_STR +#define SPIFFS_TEST_VIS_FREE_STR "_" +#endif +// spiffs_printf argument for a deleted page +#ifndef SPIFFS_TEST_VIS_DELE_STR +#define SPIFFS_TEST_VIS_DELE_STR "/" +#endif +// spiffs_printf argument for an index page for given object id +#ifndef SPIFFS_TEST_VIS_INDX_STR +#define SPIFFS_TEST_VIS_INDX_STR(id) "i" +#endif +// spiffs_printf argument for a data page for given object id +#ifndef SPIFFS_TEST_VIS_DATA_STR +#define SPIFFS_TEST_VIS_DATA_STR(id) "d" +#endif +#endif + +// Types depending on configuration such as the amount of flash bytes +// given to spiffs file system in total (spiffs_file_system_size), +// the logical block size (log_block_size), and the logical page size +// (log_page_size) + +// Block index type. Make sure the size of this type can hold +// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size +typedef u16_t spiffs_block_ix; +// Page index type. Make sure the size of this type can hold +// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size +typedef u16_t spiffs_page_ix; +// Object id type - most significant bit is reserved for index flag. Make sure the +// size of this type can hold the highest object id on a full system, +// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 +typedef u16_t spiffs_obj_id; +// Object span index type. Make sure the size of this type can +// hold the largest possible span index on the system - +// i.e. (spiffs_file_system_size / log_page_size) - 1 +typedef u16_t spiffs_span_ix; + +#endif /* SPIFFS_CONFIG_H_ */ diff --git a/components/spiffs/spiffs_gc.c b/components/spiffs/spiffs_gc.c new file mode 100644 index 0000000..db1af4c --- /dev/null +++ b/components/spiffs/spiffs_gc.c @@ -0,0 +1,606 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +// Erases a logical block and updates the erase counter. +// If cache is enabled, all pages that might be cached in this block +// is dropped. +static s32_t spiffs_gc_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + + SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix); + res = spiffs_erase_block(fs, bix); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_CACHE + { + u32_t i; + for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) { + spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i); + } + } +#endif + return res; +} + +// Searches for blocks where all entries are deleted - if one is found, +// the block is erased. Compared to the non-quick gc, the quick one ensures +// that no updates are needed on existing objects on pages that are erased. +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + + SPIFFS_GC_DBG("gc_quick: running\n"); +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // find fully deleted blocks + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t free_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else if (obj_id == SPIFFS_OBJ_ID_FREE) { + // kill scan, go for next block + free_pages_in_block++; + if (free_pages_in_block > max_free_pages) { + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + } else { + // kill scan, go for next block + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + if (res == SPIFFS_OK && + deleted_pages_in_block + free_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs) && + free_pages_in_block <= max_free_pages) { + // found a fully deleted block + fs->stats_p_deleted -= deleted_pages_in_block; + res = spiffs_gc_erase_block(fs, cur_block); + return res; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + if (res == SPIFFS_OK) { + res = SPIFFS_ERR_NO_DELETED_BLOCKS; + } + return res; +} + +// Checks if garbage collecting is necessary. If so a candidate block is found, +// cleansed and erased +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len) { + s32_t res; + s32_t free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) + - fs->stats_p_allocated - fs->stats_p_deleted; + int tries = 0; + + if (fs->free_blocks > 3 && + (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + return SPIFFS_OK; + } + + u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); +// if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { +// SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); +// return SPIFFS_ERR_FULL; +// } + if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { + SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); + return SPIFFS_ERR_FULL; + } + + do { + SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n", + tries, + fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), + len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs))); + + spiffs_block_ix *cands; + int count; + spiffs_block_ix cand; + s32_t prev_free_pages = free_pages; + // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state + res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); + SPIFFS_CHECK_RES(res); + if (count == 0) { + SPIFFS_GC_DBG("gc_check: no candidates, return\n"); + return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL; + } +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + cand = cands[0]; + fs->cleaning = 1; + //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand); + res = spiffs_gc_clean(fs, cand); + fs->cleaning = 0; + if (res < 0) { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } else { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_page_stats(fs, cand); + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_block(fs, cand); + SPIFFS_CHECK_RES(res); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + + if (prev_free_pages <= 0 && prev_free_pages == free_pages) { + // abort early to reduce wear, at least tried once + SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n"); + break; + } + + } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || + (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + res = SPIFFS_ERR_FULL; + } + + SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n", + fs->stats_p_allocated + fs->stats_p_deleted, + fs->free_blocks, free_pages, tries, res); + + return res; +} + +// Updates page statistics for a block that is about to be erased +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + int obj_lookup_page = 0; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + u32_t dele = 0; + u32_t allo = 0; + + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + dele++; + } else { + allo++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele); + fs->stats_p_allocated -= allo; + fs->stats_p_deleted -= dele; + return res; +} + +// Finds block candidates to erase +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidates, + int *candidate_count, + char fs_crammed) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + + // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score + int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t))); + *candidate_count = 0; + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + // divide up work area into block indices and scores + spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; + s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); + + // align cand_scores on s32_t boundary + cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); + + *block_candidates = cand_blocks; + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t used_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + // when a free entry is encountered, scan logic ensures that all following entries are free also + res = 1; // kill object lu loop + break; + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else { + used_pages_in_block++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + // calculate score and insert into candidate table + // stoneage sort, but probably not so many blocks + if (res == SPIFFS_OK /*&& deleted_pages_in_block > 0*/) { + // read erase count + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, cur_block), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + spiffs_obj_id erase_age; + if (fs->max_erase_count > erase_count) { + erase_age = fs->max_erase_count - erase_count; + } else { + erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count); + } + + s32_t score = + deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET + + used_pages_in_block * SPIFFS_GC_HEUR_W_USED + + erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); + int cand_ix = 0; + SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); + while (cand_ix < max_candidates) { + if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } else if (cand_scores[cand_ix] < score) { + int reorder_cand_ix = max_candidates - 2; + while (reorder_cand_ix >= cand_ix) { + cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix]; + cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix]; + reorder_cand_ix--; + } + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } + cand_ix++; + } + (*candidate_count)++; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + return res; +} + +typedef enum { + FIND_OBJ_DATA, + MOVE_OBJ_DATA, + MOVE_OBJ_IX, + FINISHED +} spiffs_gc_clean_state; + +typedef struct { + spiffs_gc_clean_state state; + spiffs_obj_id cur_obj_id; + spiffs_span_ix cur_objix_spix; + spiffs_page_ix cur_objix_pix; + spiffs_page_ix cur_data_pix; + int stored_scan_entry_index; + u8_t obj_id_found; +} spiffs_gc; + +// Empties given block by moving all data into free pages of another block +// Strategy: +// loop: +// scan object lookup for object data pages +// for first found id, check spix and load corresponding object index page to memory +// push object scan lookup entry index +// rescan object lookup, find data pages with same id and referenced by same object index +// move data page, update object index in memory +// when reached end of lookup, store updated object index +// pop object scan lookup entry index +// repeat loop until end of object lookup +// scan object lookup again for remaining object index pages, move to new page in other block +// +s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + // this is the global localizer being pushed and popped + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_gc gc; // our stack frame/state + spiffs_page_ix cur_pix = 0; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix); + + memset(&gc, 0, sizeof(spiffs_gc)); + gc.state = FIND_OBJ_DATA; + + if (fs->free_cursor_block_ix == bix) { + // move free cursor to next block, cannot use free pages from the block we want to clean + fs->free_cursor_block_ix = (bix+1)%fs->block_count; + fs->free_cursor_obj_lu_entry = 0; + SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix); + } + + while (res == SPIFFS_OK && gc.state != FINISHED) { + SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry); + gc.obj_id_found = 0; // reset (to no found data page) + + // scan through lookup pages + int obj_lookup_page = cur_entry / entries_per_page; + u8_t scan = 1; + // check each object lookup page + while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each object lookup entry + while (scan && res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); + + // act upon object id depending on gc state + switch (gc.state) { + case FIND_OBJ_DATA: + // find a data page + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { + // found a data page, stop scanning and handle in switch case below + SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id); + gc.obj_id_found = 1; + gc.cur_obj_id = obj_id; + gc.cur_data_pix = cur_pix; + scan = 0; + } + break; + case MOVE_OBJ_DATA: + // evacuate found data pages for corresponding object index we have in memory, + // update memory representation + if (obj_id == gc.cur_obj_id) { + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { + SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); + } else { + spiffs_page_ix new_data_pix; + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); + SPIFFS_CHECK_RES(res); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + new_data_pix = SPIFFS_OBJ_ID_FREE; + } + // update memory representation of object index page with new data page + if (gc.cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } + } + } + break; + case MOVE_OBJ_IX: + // find and evacuate object index pages + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + // found an index object id + spiffs_page_header p_hdr; + spiffs_page_ix new_pix; + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr, + SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + if (res == SPIFFS_OK) { + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); + } + } + SPIFFS_CHECK_RES(res); + } + break; + default: + scan = 0; + break; + } // switch gc state + cur_entry++; + } // per entry + obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop + } // per object lookup page + if (res != SPIFFS_OK) break; + + // state finalization and switch + switch (gc.state) { + case FIND_OBJ_DATA: + if (gc.obj_id_found) { + // handle found data page - + // find out corresponding obj ix page and load it to memory + spiffs_page_header p_hdr; + spiffs_page_ix objix_pix; + gc.stored_scan_entry_index = cur_entry; // push cursor + cur_entry = 0; // restart scan from start + gc.state = MOVE_OBJ_DATA; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); + SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix); + res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // on borked systems we might get an ERR_NOT_FOUND here - + // this is handled by simply deleting the page as it is not referenced + // from anywhere + SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix); + res = spiffs_page_delete(fs, gc.cur_data_pix); + SPIFFS_CHECK_RES(res); + // then we restore states and continue scanning for data pages + cur_entry = gc.stored_scan_entry_index; // pop cursor + gc.state = FIND_OBJ_DATA; + break; // done + } + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + // cannot allow a gc if the presumed index in fact is no index, a + // check must run or lot of data may be lost + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); + gc.cur_objix_pix = objix_pix; + } else { + // no more data pages found, passed thru all block, start evacuating object indices + gc.state = MOVE_OBJ_IX; + cur_entry = 0; // restart entry scan index + } + break; + case MOVE_OBJ_DATA: { + // store modified objix (hdr) page residing in memory now that all + // data pages belonging to this object index and residing in the block + // we want to evacuate + spiffs_page_ix new_objix_pix; + gc.state = FIND_OBJ_DATA; + cur_entry = gc.stored_scan_entry_index; // pop cursor + if (gc.cur_objix_spix == 0) { + // store object index header page + res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0); + SPIFFS_CHECK_RES(res); + } else { + // store object index page + res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + break; + case MOVE_OBJ_IX: + // scanned thru all block, no more object indices found - our work here is done + gc.state = FINISHED; + break; + default: + cur_entry = 0; + break; + } // switch gc.state + SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state); + } // while state != FINISHED + + + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/components/spiffs/spiffs_hydrogen.c b/components/spiffs/spiffs_hydrogen.c new file mode 100644 index 0000000..9ff3e7a --- /dev/null +++ b/components/spiffs/spiffs_hydrogen.c @@ -0,0 +1,1405 @@ +/* + * spiffs_hydrogen.c + * + * Created on: Jun 16, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0) +#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0) +#else +#define SPIFFS_FH_OFFS(fs, fh) (fh) +#define SPIFFS_FH_UNOFFS(fs, fh) (fh) +#endif + +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh); +#endif + +#if SPIFFS_BUFFER_HELP +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) { + return num_descs * sizeof(spiffs_fd); +} +#if SPIFFS_CACHE +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages) { + return sizeof(spiffs_cache) + num_pages * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)); +} +#endif +#endif + +u8_t SPIFFS_mounted(spiffs *fs) { + return SPIFFS_CHECK_MOUNT(fs); +} + +s32_t SPIFFS_format(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + if (SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_MOUNTED; + return -1; + } + + s32_t res; + SPIFFS_LOCK(fs); + + spiffs_block_ix bix = 0; + while (bix < fs->block_count) { + fs->max_erase_count = 0; + res = spiffs_erase_block(fs, bix); + if (res != SPIFFS_OK) { + res = SPIFFS_ERR_ERASE_FAIL; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + bix++; + } + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_probe_fs(spiffs_config *config) { + s32_t res = spiffs_probe(config); + return res; +} + +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f) { + void *user_data; + SPIFFS_LOCK(fs); + user_data = fs->user_data; + memset(fs, 0, sizeof(spiffs)); + memcpy(&fs->cfg, config, sizeof(spiffs_config)); + fs->user_data = user_data; + fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); + fs->work = &work[0]; + fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; + memset(fd_space, 0, fd_space_size); + // align fd_space pointer to pointer size byte boundary + u8_t ptr_size = sizeof(void*); + u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1); + if (addr_lsb) { + fd_space += (ptr_size-addr_lsb); + fd_space_size -= (ptr_size-addr_lsb); + } + fs->fd_space = fd_space; + fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); + + // align cache pointer to 4 byte boundary + addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1); + if (addr_lsb) { + u8_t *cache_8 = (u8_t *)cache; + cache_8 += (ptr_size-addr_lsb); + cache = cache_8; + cache_size -= (ptr_size-addr_lsb); + } + if (cache_size & (ptr_size-1)) { + cache_size -= (cache_size & (ptr_size-1)); + } + +#if SPIFFS_CACHE + fs->cache = cache; + fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size; + spiffs_cache_init(fs); +#endif + + s32_t res; + +#if SPIFFS_USE_MAGIC + res = SPIFFS_CHECK_MAGIC_POSSIBLE(fs) ? SPIFFS_OK : SPIFFS_ERR_MAGIC_NOT_POSSIBLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + fs->config_magic = SPIFFS_CONFIG_MAGIC; + + res = spiffs_obj_lu_scan(fs); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_DBG("page index byte len: "_SPIPRIi"\n", (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)); + SPIFFS_DBG("object lookup pages: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_LOOKUP_PAGES(fs)); + SPIFFS_DBG("page pages per block: "_SPIPRIi"\n", (u32_t)SPIFFS_PAGES_PER_BLOCK(fs)); + SPIFFS_DBG("page header length: "_SPIPRIi"\n", (u32_t)sizeof(spiffs_page_header)); + SPIFFS_DBG("object header index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_HDR_IX_LEN(fs)); + SPIFFS_DBG("object index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_IX_LEN(fs)); + SPIFFS_DBG("available file descriptors: "_SPIPRIi"\n", (u32_t)fs->fd_count); + SPIFFS_DBG("free blocks: "_SPIPRIi"\n", (u32_t)fs->free_blocks); + + fs->check_cb_f = check_cb_f; + + fs->mounted = 1; + + SPIFFS_UNLOCK(fs); + + return 0; +} + +void SPIFFS_unmount(spiffs *fs) { + if (!SPIFFS_CHECK_CFG(fs) || !SPIFFS_CHECK_MOUNT(fs)) return; + SPIFFS_LOCK(fs); + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0) { +#if SPIFFS_CACHE + (void)spiffs_fflush_cache(fs, cur_fd->file_nbr); +#endif + spiffs_fd_return(fs, cur_fd->file_nbr); + } + } + fs->mounted = 0; + + SPIFFS_UNLOCK(fs); +} + +s32_t SPIFFS_errno(spiffs *fs) { + return fs->err_code; +} + +void SPIFFS_clearerr(spiffs *fs) { + fs->err_code = SPIFFS_OK; +} + +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; (void)mode; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + spiffs_obj_id obj_id; + s32_t res; + + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (const u8_t*)path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) { + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + +#if SPIFFS_READ_ONLY + // not valid flags in read only mode + flags &= ~(SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC); +#endif // SPIFFS_READ_ONLY + + s32_t res = spiffs_fd_find_new(fs, &fd, path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if ((flags & SPIFFS_O_CREAT) == 0) { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (res == SPIFFS_OK && + (flags & (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) == (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) { + // creat and excl and file exists - fail + res = SPIFFS_ERR_FILE_EXISTS; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((flags & SPIFFS_O_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { +#if !SPIFFS_READ_ONLY + spiffs_obj_id obj_id; + // no need to enter conflicting name here, already looked for it above + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, &pix); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + flags &= ~SPIFFS_O_TRUNC; +#endif // !SPIFFS_READ_ONLY + } else { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, e->pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (SPIFFS_IS_LOOKUP_PAGE(fs, page_ix)) { + res = SPIFFS_ERR_NOT_A_FILE; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_open_by_page(fs, page_ix, fd, flags, mode); + if (res == SPIFFS_ERR_IS_FREE || + res == SPIFFS_ERR_DELETED || + res == SPIFFS_ERR_NOT_FINALIZED || + res == SPIFFS_ERR_NOT_INDEX || + res == SPIFFS_ERR_INDEX_SPAN_MISMATCH) { + res = SPIFFS_ERR_NOT_A_FILE; + } + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +static s32_t spiffs_hydro_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_RDONLY) == 0) { + res = SPIFFS_ERR_NOT_READABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (fd->size == SPIFFS_UNDEFINED_LEN && len > 0) { + // special case for zero sized files + res = SPIFFS_ERR_END_OF_OBJECT; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + if (fd->fdoffset + len >= fd->size) { + // reading beyond file size + s32_t avail = fd->size - fd->fdoffset; + if (avail <= 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT); + } + res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + fd->fdoffset += avail; + SPIFFS_UNLOCK(fs); + return avail; + } else { + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + len = avail; + } + } else { + // reading within file size + res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return len; +} + +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + s32_t res = spiffs_hydro_read(fs, fh, buf, len); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + res = 0; + } + return res; +} + + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) { + (void)fs; + s32_t res = SPIFFS_OK; + s32_t remaining = len; + if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) { + s32_t m_len = MIN((s32_t)(fd->size - offset), len); + res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len); + SPIFFS_CHECK_RES(res); + remaining -= m_len; + u8_t *buf_8 = (u8_t *)buf; + buf_8 += m_len; + buf = buf_8; + offset += m_len; + } + if (remaining > 0) { + res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining); + SPIFFS_CHECK_RES(res); + } + return len; + +} +#endif // !SPIFFS_READ_ONLY + +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)buf; (void)len; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + u32_t offset; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((fd->flags & SPIFFS_O_APPEND)) { + fd->fdoffset = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + } + + offset = fd->fdoffset; + +#if SPIFFS_CACHE_WR + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } +#endif + if (fd->flags & SPIFFS_O_APPEND) { + if (fd->size == SPIFFS_UNDEFINED_LEN) { + offset = 0; + } else { + offset = fd->size; + } +#if SPIFFS_CACHE_WR + if (fd->cache_page) { + offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size); + } +#endif + } + +#if SPIFFS_CACHE_WR + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (len < (s32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + // small write, try to cache it + u8_t alloc_cpage = 1; + if (fd->cache_page) { + // have a cached page for this fd already, check cache page boundaries + if (offset < fd->cache_page->offset || // writing before cache + offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache + offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page + { + // boundary violation, write back cache first and allocate new + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", boundary viol, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // writing within cache + alloc_cpage = 0; + } + } + + if (alloc_cpage) { + fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd); + if (fd->cache_page) { + fd->cache_page->offset = offset; + fd->cache_page->size = 0; + SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id); + } + } + + if (fd->cache_page) { + u32_t offset_in_cpage = offset - fd->cache_page->offset; + SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", offs "_SPIPRIi":"_SPIPRIi" len "_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, + offset, offset_in_cpage, len); + spiffs_cache *cache = spiffs_get_cache(fs); + u8_t *cpage_data = spiffs_get_cache_page(fs, cache, fd->cache_page->ix); + memcpy(&cpage_data[offset_in_cpage], buf, len); + fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return len; + } else { + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return res; + } + } else { + // big write, no need to cache it - but first check if there is a cached write already + if (fd->cache_page) { + // write back cache first + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", big write, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + // data written below + } + } + } +#endif + + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + s32_t fileSize = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + + switch (whence) { + case SPIFFS_SEEK_CUR: + offs = fd->fdoffset+offs; + break; + case SPIFFS_SEEK_END: + offs = fileSize + offs; + break; + } + + if ((offs > fileSize)) { + fd->fdoffset = fileSize; + res = SPIFFS_ERR_END_OF_OBJECT; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (fd->cursor_objix_spix != objix_spix) { + spiffs_page_ix pix; + res = spiffs_obj_lu_find_id_and_span( + fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->cursor_objix_spix = objix_spix; + fd->cursor_objix_pix = pix; + } + fd->fdoffset = offs; + + SPIFFS_UNLOCK(fs); + + return offs; +} + +s32_t SPIFFS_remove(spiffs *fs, const char *path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + s32_t res; + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0,0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_truncate(fd, 0, 1); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_cache_fd_release(fs, fd->cache_page); +#endif + + res = spiffs_object_truncate(fd, 0, 1); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) { + (void)fh; + spiffs_page_object_ix_header objix_hdr; + spiffs_obj_id obj_id; + s32_t res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_API_CHECK_RES(fs, res); + + u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , pix)) + + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_obj_id); + res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, fh, + obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id); + SPIFFS_API_CHECK_RES(fs, res); + + s->obj_id = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + s->type = objix_hdr.type; + s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + s->pix = pix; + strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + memcpy(s->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + + return res; +} + +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_page_ix pix; + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_stat_pix(fs, pix, 0, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + res = spiffs_stat_pix(fs, fd->objix_hdr_pix, fh, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +// Checks if there are any cached writes for the object id associated with +// given filehandle. If so, these writes are flushed. +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { + (void)fs; + (void)fh; + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES(fs, res); + + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } + if (fd->cache_page) { + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", flush, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + if (res < SPIFFS_OK) { + fs->err_code = res; + } + spiffs_cache_fd_release(fs, fd->cache_page); + } + } +#endif + + return res; +} +#endif + +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { + (void)fh; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + SPIFFS_LOCK(fs); + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs,res); + SPIFFS_UNLOCK(fs); +#endif + + return res; +} + +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + + s32_t res = SPIFFS_OK; + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); +#if SPIFFS_CACHE + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + res = spiffs_fd_return(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_rename(spiffs *fs, const char *old_path, const char *new_path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)old_path; (void)new_path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(new_path) > SPIFFS_OBJ_NAME_LEN - 1 || + strlen(old_path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_page_ix pix_old, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)old_path, &pix_old); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)new_path, &pix_dummy); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + } else if (res == SPIFFS_OK) { + res = SPIFFS_ERR_CONFLICTING_NAME; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix_old, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (const u8_t*)new_path, + 0, 0, &pix_dummy); +#if SPIFFS_TEMPORAL_FD_CACHE + if (res == SPIFFS_OK) { + spiffs_fd_temporal_cache_rehash(fs, old_path, new_path); + } +#endif + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_OBJ_META_LEN +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)name; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_page_ix pix, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)name, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_fd *fd; + spiffs_page_ix pix_dummy; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} +#endif // SPIFFS_OBJ_META_LEN + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) { + (void)name; + + if (!SPIFFS_CHECK_CFG((fs))) { + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; + return 0; + } + + if (!SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + + d->fs = fs; + d->block = 0; + d->entry = 0; + return d; +} + +static s32_t spiffs_read_dir_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + if (res != SPIFFS_OK) return res; + if ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && + objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + struct spiffs_dirent *e = (struct spiffs_dirent*)user_var_p; + e->obj_id = obj_id; + strcpy((char *)e->name, (char *)objix_hdr.name); + e->type = objix_hdr.type; + e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + e->pix = pix; +#if SPIFFS_OBJ_META_LEN + memcpy(e->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + return SPIFFS_OK; + } + return SPIFFS_VIS_COUNTINUE; +} + +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { + if (!SPIFFS_CHECK_MOUNT(d->fs)) { + d->fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + SPIFFS_LOCK(d->fs); + + spiffs_block_ix bix; + int entry; + s32_t res; + struct spiffs_dirent *ret = 0; + + res = spiffs_obj_lu_find_entry_visitor(d->fs, + d->block, + d->entry, + SPIFFS_VIS_NO_WRAP, + 0, + spiffs_read_dir_v, + 0, + e, + &bix, + &entry); + if (res == SPIFFS_OK) { + d->block = bix; + d->entry = entry + 1; + e->obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + ret = e; + } else { + d->fs->err_code = res; + } + SPIFFS_UNLOCK(d->fs); + return ret; +} + +s32_t SPIFFS_closedir(spiffs_DIR *d) { + SPIFFS_API_CHECK_CFG(d->fs); + SPIFFS_API_CHECK_MOUNT(d->fs); + return 0; +} + +s32_t SPIFFS_check(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_lookup_consistency_check(fs, 0); + + res = spiffs_object_index_consistency_check(fs); + + res = spiffs_page_consistency_check(fs); + + res = spiffs_obj_lu_scan(fs); + + SPIFFS_UNLOCK(fs); + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs); + u32_t blocks = fs->block_count; + u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs); + u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs); + u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page + + if (total) { + *total = total_data_pages * data_page_size; + } + + if (used) { + *used = fs->stats_p_allocated * data_page_size; + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) { +#if SPIFFS_READ_ONLY + (void)fs; (void)max_free_pages; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_quick(fs, max_free_pages); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + + +s32_t SPIFFS_gc(spiffs *fs, u32_t size) { +#if SPIFFS_READ_ONLY + (void)fs; (void)size; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_check(fs, size); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = (fd->fdoffset >= (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size)); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = fd->fdoffset; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func) { + SPIFFS_LOCK(fs); + fs->file_cb_f = cb_func; + SPIFFS_UNLOCK(fs); + return 0; +} + +#if SPIFFS_IX_MAP + +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_MAPPED); + } + + map->map_buf = map_buf; + map->offset = offset; + // nb: spix range includes last + map->start_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + map->end_spix = (offset + len) / SPIFFS_DATA_PAGE_SIZE(fs); + memset(map_buf, 0, sizeof(spiffs_page_ix) * (map->end_spix - map->start_spix + 1)); + fd->ix_map = map; + + // scan for pixes + res = spiffs_populate_ix_map(fs, fd, 0, map->end_spix - map->start_spix + 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + fd->ix_map = 0; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offset) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + spiffs_ix_map *map = fd->ix_map; + + s32_t spix_diff = offset / SPIFFS_DATA_PAGE_SIZE(fs) - map->start_spix; + map->offset = offset; + + // move existing pixes if within map offs + if (spix_diff != 0) { + // move vector + int i; + const s32_t vec_len = map->end_spix - map->start_spix + 1; // spix range includes last + map->start_spix += spix_diff; + map->end_spix += spix_diff; + if (spix_diff >= vec_len) { + // moving beyond range + memset(&map->map_buf, 0, vec_len * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else if (spix_diff > 0) { + // diff positive + for (i = 0; i < vec_len - spix_diff; i++) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[vec_len - spix_diff], 0, spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, vec_len - spix_diff, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // diff negative + for (i = vec_len - 1; i >= -spix_diff; i--) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[0], 0, -spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, -spix_diff - 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes) { + SPIFFS_API_CHECK_CFG(fs); + // always add one extra page, the offset might change to the middle of a page + return (bytes + SPIFFS_DATA_PAGE_SIZE(fs) ) / SPIFFS_DATA_PAGE_SIZE(fs); +} + +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries) { + SPIFFS_API_CHECK_CFG(fs); + return map_page_ix_entries * SPIFFS_DATA_PAGE_SIZE(fs); +} + +#endif // SPIFFS_IX_MAP + +#if SPIFFS_TEST_VISUALISATION +s32_t SPIFFS_vis(spiffs *fs) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_block_ix bix = 0; + + while (bix < fs->block_count) { + // check each object lookup page + int obj_lookup_page = 0; + int cur_entry = 0; + + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (cur_entry == 0) { + spiffs_printf(_SPIPRIbl" ", bix); + } else if ((cur_entry & 0x3f) == 0) { + spiffs_printf(" "); + } + if (obj_id == SPIFFS_OBJ_ID_FREE) { + spiffs_printf(SPIFFS_TEST_VIS_FREE_STR); + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + spiffs_printf(SPIFFS_TEST_VIS_DELE_STR); + } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){ + spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id)); + } else { + spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id)); + } + cur_entry++; + if ((cur_entry & 0x3f) == 0) { + spiffs_printf("\n"); + } + } // per entry + obj_lookup_page++; + } // per object lookup page + + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + if (erase_count != (spiffs_obj_id)-1) { + spiffs_printf("\tera_cnt: "_SPIPRIi"\n", erase_count); + } else { + spiffs_printf("\tera_cnt: N/A\n"); + } + + bix++; + } // per block + + spiffs_printf("era_cnt_max: "_SPIPRIi"\n", fs->max_erase_count); + spiffs_printf("last_errno: "_SPIPRIi"\n", fs->err_code); + spiffs_printf("blocks: "_SPIPRIi"\n", fs->block_count); + spiffs_printf("free_blocks: "_SPIPRIi"\n", fs->free_blocks); + spiffs_printf("page_alloc: "_SPIPRIi"\n", fs->stats_p_allocated); + spiffs_printf("page_delet: "_SPIPRIi"\n", fs->stats_p_deleted); + SPIFFS_UNLOCK(fs); + u32_t total, used; + SPIFFS_info(fs, &total, &used); + spiffs_printf("used: "_SPIPRIi" of "_SPIPRIi"\n", used, total); + return res; +} +#endif diff --git a/components/spiffs/spiffs_nucleus.c b/components/spiffs/spiffs_nucleus.c new file mode 100644 index 0000000..44ba711 --- /dev/null +++ b/components/spiffs/spiffs_nucleus.c @@ -0,0 +1,2327 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_REF_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_REF_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_REF_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_DATA(ph, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, spix); +#endif + return res; +} + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix); +#endif + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_CACHE + +s32_t spiffs_phys_rd( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *dst) { + return SPIFFS_HAL_READ(fs, addr, len, dst); +} + +s32_t spiffs_phys_wr( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *src) { + return SPIFFS_HAL_WRITE(fs, addr, len, src); +} + +#endif + +#if !SPIFFS_READ_ONLY +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len) { + (void)fh; + s32_t res; + u8_t b[SPIFFS_COPY_BUFFER_STACK]; + while (len > 0) { + u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, fh, dst, chunk_size, b); + SPIFFS_CHECK_RES(res); + len -= chunk_size; + src += chunk_size; + dst += chunk_size; + } + return SPIFFS_OK; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id with visitor. +// Iterate over object lookup pages in each block until a given object id entry is found. +// When found, the visitor function is called with block index, entry index and user data. +// If visitor returns SPIFFS_VIS_CONTINUE, the search goes on. Otherwise, the search will be +// ended and visitor's return code is returned to caller. +// If no visitor is given (0) the search returns on first entry with matching object id. +// If no match is found in all look up, SPIFFS_VIS_END is returned. +// @param fs the file system +// @param starting_block the starting block to start search in +// @param starting_lu_entry the look up index entry to start search in +// @param flags ored combination of SPIFFS_VIS_CHECK_ID, SPIFFS_VIS_CHECK_PH, +// SPIFFS_VIS_NO_WRAP +// @param obj_id argument object id +// @param v visitor callback function +// @param user_const_p any const pointer, passed to the callback visitor function +// @param user_var_p any pointer, passed to the callback visitor function +// @param block_ix reported block index where match was found +// @param lu_entry reported look up index where match was found +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = SPIFFS_OK; + s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs); + spiffs_block_ix cur_block = starting_block; + u32_t cur_block_addr = starting_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = starting_lu_entry; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // wrap initial + if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { + cur_entry = 0; + cur_block++; + cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } + + // check each block + while (res == SPIFFS_OK && entry_count > 0) { + int obj_lookup_page = cur_entry / entries_per_page; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages + cur_entry < (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page + { + if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) { + if (block_ix) *block_ix = cur_block; + if (lu_entry) *lu_entry = cur_entry; + if (v) { + res = v( + fs, + (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset], + cur_block, + cur_entry, + user_const_p, + user_var_p); + if (res == SPIFFS_VIS_COUNTINUE || res == SPIFFS_VIS_COUNTINUE_RELOAD) { + if (res == SPIFFS_VIS_COUNTINUE_RELOAD) { + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } + res = SPIFFS_OK; + cur_entry++; + entry_count--; + continue; + } else { + return res; + } + } else { + return SPIFFS_OK; + } + } + entry_count--; + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } // per block + + SPIFFS_CHECK_RES(res); + + return SPIFFS_VIS_END; +} + +#if !SPIFFS_READ_ONLY +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, bix); + s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + // here we ignore res, just try erasing the block + while (size > 0) { + SPIFFS_DBG("erase "_SPIPRIad":"_SPIPRIi"\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + SPIFFS_HAL_ERASE(fs, addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + + addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs); + size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs); + } + fs->free_blocks++; + + // register erase count for this block + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&fs->max_erase_count); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_USE_MAGIC + // finally, write magic + spiffs_obj_id magic = SPIFFS_MAGIC(fs, bix); + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_MAGIC_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&magic); + SPIFFS_CHECK_RES(res); +#endif + + fs->max_erase_count++; + if (fs->max_erase_count == SPIFFS_OBJ_ID_IX_FLAG) { + fs->max_erase_count = 0; + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +s32_t spiffs_probe( + spiffs_config *cfg) { + s32_t res; + u32_t paddr; + spiffs dummy_fs; // create a dummy fs struct just to be able to use macros + memcpy(&dummy_fs.cfg, cfg, sizeof(spiffs_config)); + dummy_fs.block_count = 0; + + // Read three magics, as one block may be in an aborted erase state. + // At least two of these must contain magic and be in decreasing order. + spiffs_obj_id magic[3]; + spiffs_obj_id bix_count[3]; + + spiffs_block_ix bix; + for (bix = 0; bix < 3; bix++) { + paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix); +#if SPIFFS_HAL_CALLBACK_EXTRA + // not any proper fs to report here, so callback with null + // (cross fingers that no-one gets angry) + res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#else + res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#endif + bix_count[bix] = magic[bix] ^ SPIFFS_MAGIC(&dummy_fs, 0); + SPIFFS_CHECK_RES(res); + } + + // check that we have sane number of blocks + if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS; + // check that the order is correct, take aborted erases in calculation + // first block aborted erase + if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) { + return (bix_count[1]+1) * cfg->log_block_size; + } + // second block aborted erase + if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) { + return bix_count[0] * cfg->log_block_size; + } + // third block aborted erase + if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) { + return bix_count[0] * cfg->log_block_size; + } + // no block has aborted erase + if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) { + return bix_count[0] * cfg->log_block_size; + } + + return SPIFFS_ERR_PROBE_NOT_A_FS; +} +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + + +static s32_t spiffs_obj_lu_scan_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)bix; + (void)user_const_p; + (void)user_var_p; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + if (ix_entry == 0) { + fs->free_blocks++; + // todo optimize further, return SPIFFS_NEXT_BLOCK + } + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + fs->stats_p_deleted++; + } else { + fs->stats_p_allocated++; + } + + return SPIFFS_VIS_COUNTINUE; +} + + +// Scans thru all obj lu and counts free, deleted and used pages +// Find the maximum block erase count +// Checks magic if enabled +s32_t spiffs_obj_lu_scan( + spiffs *fs) { + s32_t res; + spiffs_block_ix bix; + int entry; +#if SPIFFS_USE_MAGIC + spiffs_block_ix unerased_bix = (spiffs_block_ix)-1; +#endif + + // find out erase count + // if enabled, check magic + bix = 0; + spiffs_obj_id erase_count_final; + spiffs_obj_id erase_count_min = SPIFFS_OBJ_ID_FREE; + spiffs_obj_id erase_count_max = 0; + while (bix < fs->block_count) { +#if SPIFFS_USE_MAGIC + spiffs_obj_id magic; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_MAGIC_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&magic); + + SPIFFS_CHECK_RES(res); + if (magic != SPIFFS_MAGIC(fs, bix)) { + if (unerased_bix == (spiffs_block_ix)-1) { + // allow one unerased block as it might be powered down during an erase + unerased_bix = bix; + } else { + // more than one unerased block, bail out + SPIFFS_CHECK_RES(SPIFFS_ERR_NOT_A_FS); + } + } +#endif + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + if (erase_count != SPIFFS_OBJ_ID_FREE) { + erase_count_min = MIN(erase_count_min, erase_count); + erase_count_max = MAX(erase_count_max, erase_count); + } + bix++; + } + + if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJ_ID_FREE) { + // clean system, set counter to zero + erase_count_final = 0; + } else if (erase_count_max - erase_count_min > (SPIFFS_OBJ_ID_FREE)/2) { + // wrap, take min + erase_count_final = erase_count_min+1; + } else { + erase_count_final = erase_count_max+1; + } + + fs->max_erase_count = erase_count_final; + +#if SPIFFS_USE_MAGIC + if (unerased_bix != (spiffs_block_ix)-1) { + // found one unerased block, remedy + SPIFFS_DBG("mount: erase block "_SPIPRIbl"\n", bix); +#if SPIFFS_READ_ONLY + res = SPIFFS_ERR_RO_ABORTED_OPERATION; +#else + res = spiffs_erase_block(fs, unerased_bix); +#endif // SPIFFS_READ_ONLY + SPIFFS_CHECK_RES(res); + } +#endif + + // count blocks + + fs->free_blocks = 0; + fs->stats_p_allocated = 0; + fs->stats_p_deleted = 0; + + res = spiffs_obj_lu_find_entry_visitor(fs, + 0, + 0, + 0, + 0, + spiffs_obj_lu_scan_v, + 0, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + SPIFFS_CHECK_RES(res); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Find free object lookup entry +// Iterate over object lookup pages in each block until a free object id entry is found +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res; + if (!fs->cleaning && fs->free_blocks < 2) { + res = spiffs_gc_quick(fs, 0); + if (res == SPIFFS_ERR_NO_DELETED_BLOCKS) { + res = SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + if (fs->free_blocks < 2) { + return SPIFFS_ERR_FULL; + } + } + res = spiffs_obj_lu_find_id(fs, starting_block, starting_lu_entry, + SPIFFS_OBJ_ID_FREE, block_ix, lu_entry); + if (res == SPIFFS_OK) { + fs->free_cursor_block_ix = *block_ix; + fs->free_cursor_obj_lu_entry = (*lu_entry) + 1; + if (*lu_entry == 0) { + fs->free_blocks--; + } + } + if (res == SPIFFS_ERR_FULL) { + SPIFFS_DBG("fs full\n"); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = spiffs_obj_lu_find_entry_visitor( + fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, lu_entry); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + return res; +} + + +static s32_t spiffs_obj_lu_find_id_and_span_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + s32_t res; + spiffs_page_header ph; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + if (ph.obj_id == obj_id && + ph.span_ix == *((spiffs_span_ix*)user_var_p) && + (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET && + !((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (ph.flags & SPIFFS_PH_FLAG_IXDELE) == 0 && ph.span_ix == 0) && + (user_const_p == 0 || *((const spiffs_page_ix*)user_const_p) != pix)) { + return SPIFFS_OK; + } else { + return SPIFFS_VIS_COUNTINUE; + } +} + +// Find object lookup entry containing given id and span index +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_ID, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +// Find object lookup entry containing given id and span index in page headers only +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_PH, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if SPIFFS_IX_MAP + +// update index map of given fd with given object index data +static void spiffs_update_ix_map(spiffs *fs, + spiffs_fd *fd, spiffs_span_ix objix_spix, spiffs_page_object_ix *objix) { +#if SPIFFS_SINGLETON + (void)fs; +#endif + spiffs_ix_map *map = fd->ix_map; + spiffs_span_ix map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix); + spiffs_span_ix map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->end_spix); + + // check if updated ix is within map range + if (objix_spix < map_objix_start_spix || objix_spix > map_objix_end_spix) { + return; + } + + // update memory mapped page index buffer to new pages + + // get range of updated object index map data span indices + spiffs_span_ix objix_data_spix_start = + SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, objix_spix); + spiffs_span_ix objix_data_spix_end = objix_data_spix_start + + (objix_spix == 0 ? SPIFFS_OBJ_HDR_IX_LEN(fs) : SPIFFS_OBJ_IX_LEN(fs)); + + // calc union of object index range and index map range array + spiffs_span_ix map_spix = MAX(map->start_spix, objix_data_spix_start); + spiffs_span_ix map_spix_end = MIN(map->end_spix + 1, objix_data_spix_end); + + while (map_spix < map_spix_end) { + spiffs_page_ix objix_data_pix; + if (objix_spix == 0) { + // get data page from object index header page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix_header)))[map_spix]; + } else { + // get data page from object index page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, map_spix)]; + } + + if (objix_data_pix == (spiffs_page_ix)-1) { + // reached end of object, abort + break; + } + + map->map_buf[map_spix - map->start_spix] = objix_data_pix; + SPIFFS_DBG("map "_SPIPRIid":"_SPIPRIsp" ("_SPIPRIsp"--"_SPIPRIsp") objix.spix:"_SPIPRIsp" to pix "_SPIPRIpg"\n", + fd->obj_id, map_spix - map->start_spix, + map->start_spix, map->end_spix, + objix->p_hdr.span_ix, + objix_data_pix); + + map_spix++; + } +} + +typedef struct { + spiffs_fd *fd; + u32_t remaining_objix_pages_to_visit; + spiffs_span_ix map_objix_start_spix; + spiffs_span_ix map_objix_end_spix; +} spiffs_ix_map_populate_state; + +static s32_t spiffs_populate_ix_map_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_ix_map_populate_state *state = (spiffs_ix_map_populate_state *)user_var_p; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + + // load header to check it + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix), (u8_t *)objix); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, obj_id, objix->p_hdr.span_ix); + + // check if hdr is ok, and if objix range overlap with ix map range + if ((objix->p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE) && + objix->p_hdr.span_ix >= state->map_objix_start_spix && + objix->p_hdr.span_ix <= state->map_objix_end_spix) { + // ok, load rest of object index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + sizeof(spiffs_page_object_ix), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix), + (u8_t *)objix + sizeof(spiffs_page_object_ix)); + SPIFFS_CHECK_RES(res); + + spiffs_update_ix_map(fs, state->fd, objix->p_hdr.span_ix, objix); + + state->remaining_objix_pages_to_visit--; + SPIFFS_DBG("map "_SPIPRIid" ("_SPIPRIsp"--"_SPIPRIsp") remaining objix pages "_SPIPRIi"\n", + state->fd->obj_id, + state->fd->ix_map->start_spix, state->fd->ix_map->end_spix, + state->remaining_objix_pages_to_visit); + } + + if (res == SPIFFS_OK) { + res = state->remaining_objix_pages_to_visit ? SPIFFS_VIS_COUNTINUE : SPIFFS_VIS_END; + } + return res; +} + +// populates index map, from vector entry start to vector entry end, inclusive +s32_t spiffs_populate_ix_map(spiffs *fs, spiffs_fd *fd, u32_t vec_entry_start, u32_t vec_entry_end) { + s32_t res; + spiffs_ix_map *map = fd->ix_map; + spiffs_ix_map_populate_state state; + vec_entry_start = MIN((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_start); + vec_entry_end = MAX((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_end); + if (vec_entry_start > vec_entry_end) { + return SPIFFS_ERR_IX_MAP_BAD_RANGE; + } + state.map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_start); + state.map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_end); + state.remaining_objix_pages_to_visit = + state.map_objix_end_spix - state.map_objix_start_spix + 1; + state.fd = fd; + + res = spiffs_obj_lu_find_entry_visitor( + fs, + SPIFFS_BLOCK_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_VIS_CHECK_ID, + fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + spiffs_populate_ix_map_v, + 0, + &state, + 0, + 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + return res; +} + +#endif + + +#if !SPIFFS_READ_ONLY +// Allocates a free defined page with given obj_id +// Occupies object lookup entry and page +// data may be NULL; where only page header is stored, len and page_offs is ignored +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + int entry; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write page header + ph->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_header), (u8_t*)ph); + SPIFFS_CHECK_RES(res); + + // write page data + if (data) { + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0,SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + sizeof(spiffs_page_header) + page_offs, len, data); + SPIFFS_CHECK_RES(res); + } + + // finalize header if necessary + if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) { + ph->flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&ph->flags); + SPIFFS_CHECK_RES(res); + } + + // return written page + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page. +// If page data is null, provided header is used for metainfo and page data is physically copied. +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix) { + s32_t res; + u8_t was_final = 0; + spiffs_page_header *p_hdr; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + if (dst_pix) *dst_pix = free_pix; + + p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr; + if (page_data) { + // got page data + was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0; + // write unfinalized page + p_hdr->flags |= SPIFFS_PH_FLAG_FINAL; + p_hdr->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data); + } else { + // copy page data + res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs)); + } + SPIFFS_CHECK_RES(res); + + // mark entry in destination object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + if (was_final) { + // mark finalized in destination page + p_hdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fh, + SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr->flags); + SPIFFS_CHECK_RES(res); + } + // mark source deleted + res = spiffs_page_delete(fs, src_pix); + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Deletes a page and removes it from object lookup. +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix) { + s32_t res; + spiffs_page_header hdr; + hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); + // mark deleted entry in source object lookup + spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_DELETED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, + 0, + SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&d_obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_deleted++; + fs->stats_p_allocated--; + + // mark deleted in source page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, + 0, + SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&hdr.flags); + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Create an object index header page with empty index and undefined length +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + spiffs_page_object_ix_header oix_hdr; + int entry; + + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("create: found free page @ "_SPIPRIpg" bix:"_SPIPRIbl" entry:"_SPIPRIsp"\n", (spiffs_page_ix)SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write empty object index page + oix_hdr.p_hdr.obj_id = obj_id; + oix_hdr.p_hdr.span_ix = 0; + oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED); + oix_hdr.type = type; + oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page + strncpy((char*)oix_hdr.name, (const char*)name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(oix_hdr.meta, meta, SPIFFS_OBJ_META_LEN); + } else { + memset(oix_hdr.meta, 0xff, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + + // update page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr); + + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&oix_hdr, + SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); + + if (objix_hdr_pix) { + *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// update object index header with any combination of name/size/index +// new_objix_hdr_data may be null, if so the object index header page is loaded +// name may be null, if so name is not changed +// size may be null, if so size is not changed +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix) { + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header *objix_hdr; + spiffs_page_ix new_objix_hdr_pix; + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + if (new_objix_hdr_data) { + // object index header page already given to us, no need to load it + objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data; + } else { + // read object index header page + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + objix_hdr = (spiffs_page_object_ix_header *)fs->work; + } + + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, obj_id, 0); + + // change name + if (name) { + strncpy((char*)objix_hdr->name, (const char*)name, SPIFFS_OBJ_NAME_LEN); + } +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(objix_hdr->meta, meta, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + if (size) { + objix_hdr->size = size; + } + + // move and update page + res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, obj_id, 0, objix_hdr_pix, &new_objix_hdr_pix); + + if (res == SPIFFS_OK) { + if (new_pix) { + *new_pix = new_objix_hdr_pix; + } + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + new_objix_hdr_data ? SPIFFS_EV_IX_UPD : SPIFFS_EV_IX_UPD_HDR, + obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); + if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id_raw, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size) { +#if SPIFFS_IX_MAP == 0 + (void)objix; +#endif + // update index caches in all file descriptors + spiffs_obj_id obj_id = obj_id_raw & ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; +#if SPIFFS_TEMPORAL_FD_CACHE + if (cur_fd->score == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#else + if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#endif + if (spix == 0) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" objix_hdr_pix to "_SPIPRIpg", size:"_SPIPRIi"\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size); + cur_fd->objix_hdr_pix = new_pix; + if (new_size != 0) { + cur_fd->size = new_size; + } + } else { + cur_fd->file_nbr = 0; + cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED; + } + } + if (cur_fd->cursor_objix_spix == spix) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix); + cur_fd->cursor_objix_pix = new_pix; + } else { + cur_fd->cursor_objix_pix = 0; + } + } + } + +#if SPIFFS_IX_MAP + + // update index maps + if (ev == SPIFFS_EV_IX_UPD || ev == SPIFFS_EV_IX_NEW) { + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + // check fd opened, having ix map, match obj id + if (cur_fd->file_nbr == 0 || + cur_fd->ix_map == 0 || + (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; + SPIFFS_DBG(" callback: map ix update fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp"\n", cur_fd->file_nbr, cur_fd->obj_id, spix); + spiffs_update_ix_map(fs, cur_fd, spix, objix); + } + } + +#endif + + // callback to user if object index header + if (fs->file_cb_f && spix == 0 && (obj_id_raw & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_fileop_type op; + if (ev == SPIFFS_EV_IX_NEW) { + op = SPIFFS_CB_CREATED; + } else if (ev == SPIFFS_EV_IX_UPD || + ev == SPIFFS_EV_IX_MOV || + ev == SPIFFS_EV_IX_UPD_HDR) { + op = SPIFFS_CB_UPDATED; + } else if (ev == SPIFFS_EV_IX_DEL) { + op = SPIFFS_CB_DELETED; + } else { + SPIFFS_DBG(" callback: WARNING unknown callback event "_SPIPRIi"\n", ev); + return; // bail out + } + fs->file_cb_f(fs, op, obj_id, new_pix); + } +} + +// Open object by id +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + s32_t res = SPIFFS_OK; + spiffs_page_ix pix; + + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + + return res; +} + +// Open object by page index +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + (void)mode; + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header oix_hdr; + spiffs_obj_id obj_id; + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr); + SPIFFS_CHECK_RES(res); + + spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(fs, pix); + int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix); + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t *)&obj_id); + + fd->fs = fs; + fd->objix_hdr_pix = pix; + fd->size = oix_hdr.size; + fd->offset = 0; + fd->cursor_objix_pix = pix; + fd->cursor_objix_spix = 0; + fd->obj_id = obj_id; + fd->flags = flags; + + SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0); + + SPIFFS_DBG("open: fd "_SPIPRIfd" is obj id "_SPIPRIid"\n", fd->file_nbr, fd->obj_id); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Append to object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + SPIFFS_DBG("append: "_SPIPRIi" bytes @ offs "_SPIPRIi" of size "_SPIPRIi"\n", len, offset, fd->size); + + if (offset > fd->size) { + SPIFFS_DBG("append: offset reversed to size\n"); + offset = fd->size; + } + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); // add an extra page of data worth for meta + if (res != SPIFFS_OK) { + SPIFFS_DBG("append: gc check fail "_SPIPRIi"\n", res); + } + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_page; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_page; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index page, unless first pass + SPIFFS_DBG("append: "_SPIPRIid" store objix "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, prev_objix_spix, written); + if (prev_objix_spix == 0) { + // this is an update to object index header page + objix_hdr->size = offset+written; + if (offset == 0) { + // was an empty object, update same page (size was 0xffffffff) + res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + } else { + // was a nonempty object, update to new page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + new_objix_hdr_page, 0, written); + } + } else { + // this is an update to an object index page + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + // update length in object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new size I "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + offset+written, new_objix_hdr_page, 0, written); + } + fd->size = offset+written; + fd->offset = offset+written; + } + + // create or load new object index page + if (cur_objix_spix == 0) { + // load object index header page, must always exist + SPIFFS_DBG("append: "_SPIPRIid" load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", fd->obj_id, cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs)); + // on subsequent passes, create a new object index page + if (written > 0 || cur_objix_spix > len_objix_spix) { + p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = cur_objix_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + res = spiffs_page_allocate_data(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 1, &cur_objix_pix); + SPIFFS_CHECK_RES(res); + // quick "load" of new object index page + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header)); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); + SPIFFS_DBG("append: "_SPIPRIid" create objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + } else { + // on first pass, we load existing object index page + spiffs_page_ix pix; + SPIFFS_DBG("append: "_SPIPRIid" find objix span_ix:"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("append: "_SPIPRIid" found object index at page "_SPIPRIpg" [fd size "_SPIPRIi"]\n", fd->obj_id, pix, fd->size); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + fd->size = offset+written; + } + prev_objix_spix = cur_objix_spix; + } + + // write data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + if (page_offs == 0) { + // at beginning of a page, allocate and write a new page of data + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_page); + SPIFFS_DBG("append: "_SPIPRIid" store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id, + data_page, data_spix, page_offs, to_write, written); + } else { + // append to existing page, fill out free data in existing page + if (cur_objix_spix == 0) { + // get data page from object index header page + data_page = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_page = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + res = spiffs_page_data_check(fs, fd, data_page, data_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + SPIFFS_DBG("append: "_SPIPRIid" store to existing data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id + , data_page, data_spix, page_offs, to_write, written); + } + + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, data_spix); + objix_hdr->size = offset+written; + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->size = offset+written; + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page, unless object header index page + SPIFFS_DBG("append: "_SPIPRIid" store objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + + // update size in object header index page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store new size II "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi", res "_SPIPRIi"\n", fd->obj_id + , offset+written, new_objix_hdr_page, 0, written, res2); + SPIFFS_CHECK_RES(res2); + } else { + // wrote within object index header page + if (offset == 0) { + // wrote to empty object - simply update size and write whole page + objix_hdr->size = offset+written; + SPIFFS_DBG("append: "_SPIPRIid" store fresh objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD_HDR, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); + } else { + // modifying object index header page, update size and make new copy + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , new_objix_hdr_page, 0, written); + SPIFFS_CHECK_RES(res2); + } + } + + return res; +} // spiffs_object_append +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Modify object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_pix; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_pix; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index (header) page, unless first pass + if (prev_objix_spix == 0) { + // store previous object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res); + } else { + // store new version of previous object index page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store previous modified objix page, "_SPIPRIid":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, objix->p_hdr.span_ix, written); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + + // load next object index page + if (cur_objix_spix == 0) { + // load object index header page, must exist + SPIFFS_DBG("modify: load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + // load existing object index page on first pass + spiffs_page_ix pix; + SPIFFS_DBG("modify: find objix span_ix:"_SPIPRIsp"\n", cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("modify: found object index at page "_SPIPRIpg"\n", pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + prev_objix_spix = cur_objix_spix; + } + + // write partial data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + spiffs_page_ix orig_data_pix; + if (cur_objix_spix == 0) { + // get data page from object index header page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) { + // a full page, allocate and write a new page of data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_pix); + SPIFFS_DBG("modify: store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", data_pix, data_spix, page_offs, to_write, written); + } else { + // write to existing page, allocate new and copy unmodified data + + res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &data_pix); + if (res != SPIFFS_OK) break; + + // copy unmodified data + if (page_offs > 0) { + // before modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header), + page_offs); + if (res != SPIFFS_OK) break; + } + if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) { + // after modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write)); + if (res != SPIFFS_OK) break; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + SPIFFS_DBG("modify: store to existing data page, src:"_SPIPRIpg", dst:"_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); + } + + // delete original data page + res = spiffs_page_delete(fs, orig_data_pix); + if (res != SPIFFS_OK) break; + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", data_pix, data_spix); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page + // move and update page + spiffs_page_ix new_objix_pix; + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store modified objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, cur_objix_spix, written); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + + } else { + // wrote within object index header page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res2); + } + + return res; +} // spiffs_object_modify +#endif // !SPIFFS_READ_ONLY + +static s32_t spiffs_object_find_object_index_header_by_name_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_var_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_OK; + } + } + + return SPIFFS_VIS_COUNTINUE; +} + +// Finds object index header page by name +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + 0, + 0, + spiffs_object_find_object_index_header_by_name_v, + name, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if !SPIFFS_READ_ONLY +// Truncates object to new size. If new size is null, object may be removed totally +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_size, + u8_t remove_full) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + + if ((fd->size == SPIFFS_UNDEFINED_LEN || fd->size == 0) && !remove_full) { + // no op + return res; + } + + // need 2 pages if not removing: object index page + possibly chopped data page + if (remove_full == 0) { + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2); + SPIFFS_CHECK_RES(res); + } + + spiffs_page_ix objix_pix = fd->objix_hdr_pix; + spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_size = fd->size == (u32_t)SPIFFS_UNDEFINED_LEN ? 0 : fd->size ; + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_ix data_pix; + spiffs_page_ix new_objix_hdr_pix; + + // before truncating, check if object is to be fully removed and mark this + if (remove_full && new_size == 0) { + u8_t flags = ~( SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + SPIFFS_CHECK_RES(res); + } + + // delete from end of object until desired len is reached + while (cur_size > new_size) { + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // put object index for current data span index in work buffer + if (prev_objix_spix != cur_objix_spix) { + if (prev_objix_spix != (spiffs_span_ix)-1) { + // remove previous object index page + SPIFFS_DBG("truncate: delete objix page "_SPIPRIpg":"_SPIPRIsp"\n", objix_pix, prev_objix_spix); + + res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0); + if (prev_objix_spix > 0) { + // Update object index header page, unless we totally want to remove the file. + // If fully removing, we're not keeping consistency as good as when storing the header between chunks, + // would we be aborted. But when removing full files, a crammed system may otherwise + // report ERR_FULL a la windows. We cannot have that. + // Hence, take the risk - if aborted, a file check would free the lost pages and mend things + // as the file is marked as fully deleted in the beginning. + if (remove_full == 0) { + SPIFFS_DBG("truncate: update objix hdr page "_SPIPRIpg":"_SPIPRIsp" to size "_SPIPRIi"\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + } + } + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + + SPIFFS_DBG("truncate: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE; + } + + SPIFFS_DBG("truncate: got data pix "_SPIPRIpg"\n", data_pix); + + if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) { + // delete full data page + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_ERR_DELETED && res != SPIFFS_OK && res != SPIFFS_ERR_INDEX_REF_FREE) { + SPIFFS_DBG("truncate: err validating data pix "_SPIPRIi"\n", res); + break; + } + + if (res == SPIFFS_OK) { + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) { + SPIFFS_DBG("truncate: err deleting data pix "_SPIPRIi"\n", res); + break; + } + } else if (res == SPIFFS_ERR_DELETED || res == SPIFFS_ERR_INDEX_REF_FREE) { + res = SPIFFS_OK; + } + + // update current size + if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) { + cur_size -= SPIFFS_DATA_PAGE_SIZE(fs); + } else { + cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs); + } + fd->size = cur_size; + fd->offset = cur_size; + SPIFFS_DBG("truncate: delete data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", data_pix, data_spix, cur_size); + } else { + // delete last page, partially + spiffs_page_header p_hdr; + spiffs_page_ix new_data_pix; + u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_DBG("truncate: delete "_SPIPRIi" bytes from data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", bytes_to_remove, data_pix, data_spix, cur_size); + + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_OK) break; + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + // allocate new page and copy unmodified data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &new_data_pix); + if (res != SPIFFS_OK) break; + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove); + if (res != SPIFFS_OK) break; + // delete original data page + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + cur_size = new_size; + fd->size = new_size; + fd->offset = cur_size; + break; + } + data_spix--; + } // while all data + + // update object indices + if (cur_objix_spix == 0) { + // update object index header page + if (cur_size == 0) { + if (remove_full) { + // remove object altogether + SPIFFS_DBG("truncate: remove object index header page "_SPIPRIpg"\n", objix_pix); + + res = spiffs_page_index_check(fs, fd, objix_pix, 0); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); + } else { + // make uninitialized object + SPIFFS_DBG("truncate: reset objix_hdr page "_SPIPRIpg"\n", objix_pix); + memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff, + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header)); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update object index header page + SPIFFS_DBG("truncate: update object index header page with indices and size\n"); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update both current object index page and object index header page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res); + + // move and update object index page + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, fd->obj_id, 0, objix_pix, &new_objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + SPIFFS_DBG("truncate: store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, cur_objix_spix); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + // update object index header page with new size + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + + return res; +} // spiffs_object_truncate +#endif // !SPIFFS_READ_ONLY + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + spiffs_page_ix objix_pix; + spiffs_page_ix data_pix; + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_offset = offset; + spiffs_span_ix cur_objix_spix; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + while (cur_offset < offset + len) { +#if SPIFFS_IX_MAP + // check if we have a memory, index map and if so, if we're within index map's range + // and if so, if the entry is populated + if (fd->ix_map && data_spix >= fd->ix_map->start_spix && data_spix <= fd->ix_map->end_spix + && fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]) { + data_pix = fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]; + } else { +#endif + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (prev_objix_spix != cur_objix_spix) { + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + SPIFFS_DBG("read: find objix "_SPIPRIid":"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + objix_pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + } + SPIFFS_DBG("read: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); + + fd->offset = cur_offset; + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } +#if SPIFFS_IX_MAP + } +#endif + // all remaining data + u32_t len_to_read = offset + len - cur_offset; + // remaining data in page + len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + // remaining data in file + len_to_read = MIN(len_to_read, fd->size); + SPIFFS_DBG("read: offset:"_SPIPRIi" rd:"_SPIPRIi" data spix:"_SPIPRIsp" is data_pix:"_SPIPRIpg" addr:"_SPIPRIad"\n", cur_offset, len_to_read, data_spix, data_pix, + (u32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)))); + if (len_to_read <= 0) { + res = SPIFFS_ERR_END_OF_OBJECT; + break; + } + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + SPIFFS_CHECK_RES(res); + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)), + len_to_read, + dst); + SPIFFS_CHECK_RES(res); + dst += len_to_read; + cur_offset += len_to_read; + fd->offset = cur_offset; + data_spix++; + } + + return res; +} + +#if !SPIFFS_READ_ONLY +typedef struct { + spiffs_obj_id min_obj_id; + spiffs_obj_id max_obj_id; + u32_t compaction; + const u8_t *conflicting_name; +} spiffs_free_obj_id_state; + +static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p) { + if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) { + spiffs_obj_id min_obj_id = *((spiffs_obj_id*)user_var_p); + const u8_t *conflicting_name = (const u8_t*)user_const_p; + + // if conflicting name parameter is given, also check if this name is found in object index hdrs + if (conflicting_name && (id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + int res; + spiffs_page_object_ix_header objix_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + } + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t bit_ix = (id-min_obj_id) & 7; + int byte_ix = (id-min_obj_id) >> 3; + if (byte_ix >= 0 && (u32_t)byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + fs->work[byte_ix] |= (1<conflicting_name && strcmp((const char *)state->conflicting_name, (char *)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + if (id >= state->min_obj_id && id <= state->max_obj_id) { + u8_t *map = (u8_t *)fs->work; + int ix = (id - state->min_obj_id) / state->compaction; + //SPIFFS_DBG("free_obj_id: add ix "_SPIPRIi" for id "_SPIPRIid" min"_SPIPRIid" max"_SPIPRIid" comp:"_SPIPRIi"\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); + map[ix]++; + } + } + } + return SPIFFS_VIS_COUNTINUE; +} + +// Scans thru all object lookup for object index header pages. If total possible number of +// object ids cannot fit into a work buffer, these are grouped. When a group containing free +// object ids is found, the object lu is again scanned for object ids within group and bitmasked. +// Finally, the bitmask is searched for a free id +s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, const u8_t *conflicting_name) { + s32_t res = SPIFFS_OK; + u32_t max_objects = (fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2; + spiffs_free_obj_id_state state; + spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE; + state.min_obj_id = 1; + state.max_obj_id = max_objects + 1; + if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) { + state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG; + } + state.compaction = 0; + state.conflicting_name = conflicting_name; + while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) { + if (state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) { + // possible to represent in bitmap + u32_t i, j; + SPIFFS_DBG("free_obj_id: BITM min:"_SPIPRIid" max:"_SPIPRIid"\n", state.min_obj_id, state.max_obj_id); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, + conflicting_name, &state.min_obj_id, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + // traverse bitmask until found free obj_id + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) { + u8_t mask = fs->work[i]; + if (mask == 0xff) { + continue; + } + for (j = 0; j < 8; j++) { + if ((mask & (1<work; + u8_t min_count = 0xff; + + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) { + if (map[i] < min_count) { + min_count = map[i]; + min_i = i; + if (min_count == 0) { + break; + } + } + } + + if (min_count == state.compaction) { + // there are no free objids! + SPIFFS_DBG("free_obj_id: compacted table is full\n"); + return SPIFFS_ERR_FULL; + } + + SPIFFS_DBG("free_obj_id: COMP select index:"_SPIPRIi" min_count:"_SPIPRIi" min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); + + if (min_count == 0) { + // no id in this range, skip compacting and use directly + *obj_id = min_i * state.compaction + state.min_obj_id; + return SPIFFS_OK; + } else { + SPIFFS_DBG("free_obj_id: COMP SEL chunk:"_SPIPRIi" min:"_SPIPRIid" -> "_SPIPRIid"\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); + state.min_obj_id += min_i * state.compaction; + state.max_obj_id = state.min_obj_id + state.compaction; + // decrease compaction + } + if ((state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) { + // no need for compacting, use bitmap + continue; + } + } + // in a work memory of log_page_size bytes, we may fit in log_page_size ids + // todo what if compaction is > 255 - then we cannot fit it in a byte + state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t))); + SPIFFS_DBG("free_obj_id: COMP min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", state.min_obj_id, state.max_obj_id, state.compaction); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, &state, 0, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + state.conflicting_name = 0; // searched for conflicting name once, no need to do it again + } + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_TEMPORAL_FD_CACHE +// djb2 hash +static u32_t spiffs_hash(spiffs *fs, const u8_t *name) { + (void)fs; + u32_t hash = 5381; + u8_t c; + int i = 0; + while ((c = name[i++]) && i < SPIFFS_OBJ_NAME_LEN) { + hash = (hash * 33) ^ c; + } + return hash; +} +#endif + +s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd, const char *name) { +#if SPIFFS_TEMPORAL_FD_CACHE + u32_t i; + u16_t min_score = 0xffff; + u32_t cand_ix = (u32_t)-1; + u32_t name_hash = name ? spiffs_hash(fs, (const u8_t *)name) : 0; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + + if (name) { + // first, decrease score of all closed descriptors + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (cur_fd->score > 1) { // score == 0 indicates never used fd + cur_fd->score--; + } + } + } + } + + // find the free fd with least score + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (name && cur_fd->name_hash == name_hash) { + cand_ix = i; + break; + } + if (cur_fd->score < min_score) { + min_score = cur_fd->score; + cand_ix = i; + } + } + } + + if (cand_ix != (u32_t)-1) { + spiffs_fd *cur_fd = &fds[cand_ix]; + if (name) { + if (cur_fd->name_hash == name_hash && cur_fd->score > 0) { + // opened an fd with same name hash, assume same file + // set search point to saved obj index page and hope we have a correct match directly + // when start searching - if not, we will just keep searching until it is found + fs->cursor_block_ix = SPIFFS_BLOCK_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + fs->cursor_obj_lu_entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + // update score + if (cur_fd->score < 0xffff-SPIFFS_TEMPORAL_CACHE_HIT_SCORE) { + cur_fd->score += SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + } else { + cur_fd->score = 0xffff; + } + } else { + // no hash hit, restore this fd to initial state + cur_fd->score = SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + cur_fd->name_hash = name_hash; + } + } + cur_fd->file_nbr = cand_ix+1; + *fd = cur_fd; + return SPIFFS_OK; + } else { + return SPIFFS_ERR_OUT_OF_FILE_DESCS; + } +#else + (void)name; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + cur_fd->file_nbr = i+1; + *fd = cur_fd; + return SPIFFS_OK; + } + } + return SPIFFS_ERR_OUT_OF_FILE_DESCS; +#endif +} + +s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + spiffs_fd *fd = &fds[f-1]; + if (fd->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + fd->file_nbr = 0; +#if SPIFFS_IX_MAP + fd->ix_map = 0; +#endif + return SPIFFS_OK; +} + +s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + *fd = &fds[f-1]; + if ((*fd)->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + return SPIFFS_OK; +} + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path) { + u32_t i; + u32_t old_hash = spiffs_hash(fs, (const u8_t *)old_path); + u32_t new_hash = spiffs_hash(fs, (const u8_t *)new_path); + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->score > 0 && cur_fd->name_hash == old_hash) { + cur_fd->name_hash = new_hash; + } + } +} +#endif diff --git a/components/spiffs/spiffs_nucleus.h b/components/spiffs/spiffs_nucleus.h new file mode 100644 index 0000000..7d676ee --- /dev/null +++ b/components/spiffs/spiffs_nucleus.h @@ -0,0 +1,797 @@ +/* + * spiffs_nucleus.h + * + * Created on: Jun 15, 2013 + * Author: petera + */ + +/* SPIFFS layout + * + * spiffs is designed for following spi flash characteristics: + * - only big areas of data (blocks) can be erased + * - erasing resets all bits in a block to ones + * - writing pulls ones to zeroes + * - zeroes cannot be pulled to ones, without erase + * - wear leveling + * + * spiffs is also meant to be run on embedded, memory constraint devices. + * + * Entire area is divided in blocks. Entire area is also divided in pages. + * Each block contains same number of pages. A page cannot be erased, but a + * block can be erased. + * + * Entire area must be block_size * x + * page_size must be block_size / (2^y) where y > 2 + * + * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes + * + * BLOCK 0 PAGE 0 object lookup 1 + * PAGE 1 object lookup 2 + * ... + * PAGE n-1 object lookup n + * PAGE n object data 1 + * PAGE n+1 object data 2 + * ... + * PAGE n+m-1 object data m + * + * BLOCK 1 PAGE n+m object lookup 1 + * PAGE n+m+1 object lookup 2 + * ... + * PAGE 2n+m-1 object lookup n + * PAGE 2n+m object data 1 + * PAGE 2n+m object data 2 + * ... + * PAGE 2n+2m-1 object data m + * ... + * + * n is number of object lookup pages, which is number of pages needed to index all pages + * in a block by object id + * : block_size / page_size * sizeof(obj_id) / page_size + * m is number data pages, which is number of pages in block minus number of lookup pages + * : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size + * thus, n+m is total number of pages in a block + * : block_size / page_size + * + * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256 + * + * Object lookup pages contain object id entries. Each entry represent the corresponding + * data page. + * Assuming a 16 bit object id, an object id being 0xffff represents a free page. + * An object id being 0x0000 represents a deleted page. + * + * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff .. + * page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff .. + * page 2 : data : data for object id 0008 + * page 3 : data : data for object id 0001 + * page 4 : data : data for object id 0aaa + * ... + * + * + * Object data pages can be either object index pages or object content. + * All object data pages contains a data page header, containing object id and span index. + * The span index denotes the object page ordering amongst data pages with same object id. + * This applies to both object index pages (when index spans more than one page of entries), + * and object data pages. + * An object index page contains page entries pointing to object content page. The entry index + * in a object index page correlates to the span index in the actual object data page. + * The first object index page (span index 0) is called object index header page, and also + * contains object flags (directory/file), size, object name etc. + * + * ex: + * BLOCK 1 + * PAGE 256: objectl lookup page 1 + * [*123] [ 123] [ 123] [ 123] + * [ 123] [*123] [ 123] [ 123] + * [free] [free] [free] [free] ... + * PAGE 257: objectl lookup page 2 + * [free] [free] [free] [free] ... + * PAGE 258: object index page (header) + * obj.id:0123 span.ix:0000 flags:INDEX + * size:1600 name:ex.txt type:file + * [259] [260] [261] [262] + * PAGE 259: object data page + * obj.id:0123 span.ix:0000 flags:DATA + * PAGE 260: object data page + * obj.id:0123 span.ix:0001 flags:DATA + * PAGE 261: object data page + * obj.id:0123 span.ix:0002 flags:DATA + * PAGE 262: object data page + * obj.id:0123 span.ix:0003 flags:DATA + * PAGE 263: object index page + * obj.id:0123 span.ix:0001 flags:INDEX + * [264] [265] [fre] [fre] + * [fre] [fre] [fre] [fre] + * PAGE 264: object data page + * obj.id:0123 span.ix:0004 flags:DATA + * PAGE 265: object data page + * obj.id:0123 span.ix:0005 flags:DATA + * + */ +#ifndef SPIFFS_NUCLEUS_H_ +#define SPIFFS_NUCLEUS_H_ + +#define _SPIFFS_ERR_CHECK_FIRST (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_OBJ_ID_MISM (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_SPIX_MISM (SPIFFS_ERR_INTERNAL - 2) +#define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3) +#define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4) + +// visitor result, continue searching +#define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20) +// visitor result, continue searching after reloading lu buffer +#define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21) +// visitor result, stop searching +#define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22) + +// updating an object index contents +#define SPIFFS_EV_IX_UPD (0) +// creating a new object index +#define SPIFFS_EV_IX_NEW (1) +// deleting an object index +#define SPIFFS_EV_IX_DEL (2) +// moving an object index without updating contents +#define SPIFFS_EV_IX_MOV (3) +// updating an object index header data only, not the table itself +#define SPIFFS_EV_IX_UPD_HDR (4) + +#define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1))) + +#define SPIFFS_UNDEFINED_LEN (u32_t)(-1) + +#define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0) +#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) + +#if SPIFFS_USE_MAGIC +#if !SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs))) +#else // SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix)))) +#endif // SPIFFS_USE_MAGIC_LENGTH +#endif // SPIFFS_USE_MAGIC + +#define SPIFFS_CONFIG_MAGIC (0x20090315) + +#if SPIFFS_SINGLETON == 0 +#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \ + ((fs)->cfg.log_page_size) +#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \ + ((fs)->cfg.log_block_size) +#define SPIFFS_CFG_PHYS_SZ(fs) \ + ((fs)->cfg.phys_size) +#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \ + ((fs)->cfg.phys_erase_block) +#define SPIFFS_CFG_PHYS_ADDR(fs) \ + ((fs)->cfg.phys_addr) +#endif + +// total number of pages +#define SPIFFS_MAX_PAGES(fs) \ + ( SPIFFS_CFG_PHYS_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// total number of pages per block, including object lookup pages +#define SPIFFS_PAGES_PER_BLOCK(fs) \ + ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// number of object lookup pages per block +#define SPIFFS_OBJ_LOOKUP_PAGES(fs) \ + (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) ) +// checks if page index belongs to object lookup +#define SPIFFS_IS_LOOKUP_PAGE(fs,pix) \ + (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// number of object lookup entries in all object lookup pages +#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \ + (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// converts a block to physical address +#define SPIFFS_BLOCK_TO_PADDR(fs, block) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) ) +// converts a object lookup entry to page index +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \ + ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry)) +// converts a object lookup entry to physical address of corresponding page +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \ + (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a page to physical address +#define SPIFFS_PAGE_TO_PADDR(fs, page) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a physical address to page +#define SPIFFS_PADDR_TO_PAGE(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// gives index in page for a physical address +#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// returns containing block for given page +#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \ + ( (page) / SPIFFS_PAGES_PER_BLOCK(fs) ) +// returns starting page for block +#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \ + ( (block) * SPIFFS_PAGES_PER_BLOCK(fs) ) +// converts page to entry in object lookup page +#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \ + ( (page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) ) +// returns data size in a data page +#define SPIFFS_DATA_PAGE_SIZE(fs) \ + ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) ) +// returns physical address for block's erase count, +// always in the physical last entry of the last object lookup page +#define SPIFFS_ERASE_COUNT_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id) ) +// returns physical address for block's magic, +// always in the physical second last entry of the last object lookup page +#define SPIFFS_MAGIC_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id)*2 ) +// checks if there is any room for magic in the object luts +#define SPIFFS_CHECK_MAGIC_POSSIBLE(fs) \ + ( (SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) % (SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(spiffs_obj_id))) * sizeof(spiffs_obj_id) \ + <= (SPIFFS_CFG_LOG_PAGE_SZ(fs)-sizeof(spiffs_obj_id)*2) ) + +// define helpers object + +// entries in an object header page index +#define SPIFFS_OBJ_HDR_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix)) +// entries in an object page index +#define SPIFFS_OBJ_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix)) +// object index entry for given data span index +#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs))) +// object index span index number for given data span index or entry +#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) +// get data span index for object index span index +#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \ + ( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) ) + +#define SPIFFS_OP_T_OBJ_LU (0<<0) +#define SPIFFS_OP_T_OBJ_LU2 (1<<0) +#define SPIFFS_OP_T_OBJ_IX (2<<0) +#define SPIFFS_OP_T_OBJ_DA (3<<0) +#define SPIFFS_OP_C_DELE (0<<2) +#define SPIFFS_OP_C_UPDT (1<<2) +#define SPIFFS_OP_C_MOVS (2<<2) +#define SPIFFS_OP_C_MOVD (3<<2) +#define SPIFFS_OP_C_FLSH (4<<2) +#define SPIFFS_OP_C_READ (5<<2) +#define SPIFFS_OP_C_WRTHRU (6<<2) + +#define SPIFFS_OP_TYPE_MASK (3<<0) +#define SPIFFS_OP_COM_MASK (7<<2) + + +// if 0, this page is written to, else clean +#define SPIFFS_PH_FLAG_USED (1<<0) +// if 0, writing is finalized, else under modification +#define SPIFFS_PH_FLAG_FINAL (1<<1) +// if 0, this is an index page, else a data page +#define SPIFFS_PH_FLAG_INDEX (1<<2) +// if 0, page is deleted, else valid +#define SPIFFS_PH_FLAG_DELET (1<<7) +// if 0, this index header is being deleted +#define SPIFFS_PH_FLAG_IXDELE (1<<6) + + +#define SPIFFS_CHECK_MOUNT(fs) \ + ((fs)->mounted != 0) + +#define SPIFFS_CHECK_CFG(fs) \ + ((fs)->config_magic == SPIFFS_CONFIG_MAGIC) + +#define SPIFFS_CHECK_RES(res) \ + do { \ + if ((res) < SPIFFS_OK) return (res); \ + } while (0); + +#define SPIFFS_API_CHECK_MOUNT(fs) \ + if (!SPIFFS_CHECK_MOUNT((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \ + return SPIFFS_ERR_NOT_MOUNTED; \ + } + +#define SPIFFS_API_CHECK_CFG(fs) \ + if (!SPIFFS_CHECK_CFG((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \ + return SPIFFS_ERR_NOT_CONFIGURED; \ + } + +#define SPIFFS_API_CHECK_RES(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + return (res); \ + } + +#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + SPIFFS_UNLOCK(fs); \ + return (res); \ + } + +#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \ + if (((objid) & SPIFFS_OBJ_ID_IX_FLAG) == 0) return SPIFFS_ERR_NOT_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH; + //if ((spix) == 0 && ((ph).flags & SPIFFS_PH_FLAG_IXDELE) == 0) return SPIFFS_ERR_DELETED; + +#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \ + if ((objid) & SPIFFS_OBJ_ID_IX_FLAG) return SPIFFS_ERR_IS_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; + + +// check id, only visit matching objec ids +#define SPIFFS_VIS_CHECK_ID (1<<0) +// report argument object id to visitor - else object lookup id is reported +#define SPIFFS_VIS_CHECK_PH (1<<1) +// stop searching at end of all look up pages +#define SPIFFS_VIS_NO_WRAP (1<<2) + +#if SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len)) + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_paddr), (_len)) + +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +#if SPIFFS_CACHE + +#define SPIFFS_CACHE_FLAG_DIRTY (1<<0) +#define SPIFFS_CACHE_FLAG_WRTHRU (1<<1) +#define SPIFFS_CACHE_FLAG_OBJLU (1<<2) +#define SPIFFS_CACHE_FLAG_OBJIX (1<<3) +#define SPIFFS_CACHE_FLAG_DATA (1<<4) +#define SPIFFS_CACHE_FLAG_TYPE_WR (1<<7) + +#define SPIFFS_CACHE_PAGE_SIZE(fs) \ + (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)) + +#define spiffs_get_cache(fs) \ + ((spiffs_cache *)((fs)->cache)) + +#define spiffs_get_cache_page_hdr(fs, c, ix) \ + ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)]))) + +#define spiffs_get_cache_page(fs, c, ix) \ + ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page)) + +// cache page struct +typedef struct { + // cache flags + u8_t flags; + // cache page index + u8_t ix; + // last access of this cache page + u32_t last_access; + union { + // type read cache + struct { + // read cache page index + spiffs_page_ix pix; + }; +#if SPIFFS_CACHE_WR + // type write cache + struct { + // write cache + spiffs_obj_id obj_id; + // offset in cache page + u32_t offset; + // size of cache page + u16_t size; + }; +#endif + }; +} spiffs_cache_page; + +// cache struct +typedef struct { + u8_t cpage_count; + u32_t last_access; + u32_t cpage_use_map; + u32_t cpage_use_mask; + u8_t *cpages; +} spiffs_cache; + +#endif + + +// spiffs nucleus file descriptor +typedef struct { + // the filesystem of this descriptor + spiffs *fs; + // number of file descriptor - if 0, the file descriptor is closed + spiffs_file file_nbr; + // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted + spiffs_obj_id obj_id; + // size of the file + u32_t size; + // cached object index header page index + spiffs_page_ix objix_hdr_pix; + // cached offset object index page index + spiffs_page_ix cursor_objix_pix; + // cached offset object index span index + spiffs_span_ix cursor_objix_spix; + // current absolute offset + u32_t offset; + // current file descriptor offset + u32_t fdoffset; + // fd flags + spiffs_flags flags; +#if SPIFFS_CACHE_WR + spiffs_cache_page *cache_page; +#endif +#if SPIFFS_TEMPORAL_FD_CACHE + // djb2 hash of filename + u32_t name_hash; + // hit score (score == 0 indicates never used fd) + u16_t score; +#endif +#if SPIFFS_IX_MAP + // spiffs index map, if 0 it means unmapped + spiffs_ix_map *ix_map; +#endif +} spiffs_fd; + + +// object structs + +// page header, part of each page except object lookup pages +// NB: this is always aligned when the data page is an object index, +// as in this case struct spiffs_page_object_ix is used +typedef struct __attribute(( packed )) { + // object id + spiffs_obj_id obj_id; + // object span index + spiffs_span_ix span_ix; + // flags + u8_t flags; +} spiffs_page_header; + +// object index header page header +typedef struct __attribute(( packed )) +#if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES + __attribute(( aligned(sizeof(spiffs_page_ix)) )) +#endif +{ + // common page header + spiffs_page_header p_hdr; + // alignment + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; + // size of object + u32_t size; + // type of object + spiffs_obj_type type; + // name of object + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + // metadata. not interpreted by SPIFFS in any way. + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_page_object_ix_header; + +// object index page header +typedef struct __attribute(( packed )) { + spiffs_page_header p_hdr; + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; +} spiffs_page_object_ix; + +// callback func for object lookup visitor +typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p); + + +#if SPIFFS_CACHE +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src)) +#else +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (addr), (len), (src)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +// --------------- + +s32_t spiffs_phys_rd( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *dst); + +s32_t spiffs_phys_wr( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *src); + +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len); + +s32_t spiffs_phys_count_free_blocks( + spiffs *fs); + +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix); + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH +s32_t spiffs_probe( + spiffs_config *cfg); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH + +// --------------- + +s32_t spiffs_obj_lu_scan( + spiffs *fs); + +s32_t spiffs_obj_lu_find_free_obj_id( + spiffs *fs, + spiffs_obj_id *obj_id, + const u8_t *conflicting_name); + +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix); + +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix); + +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix); + +// --------------- + +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix); + +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix); + +#if SPIFFS_IX_MAP + +s32_t spiffs_populate_ix_map( + spiffs *fs, + spiffs_fd *fd, + u32_t vec_entry_start, + u32_t vec_entry_end); + +#endif + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size); + +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_append( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_modify( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst); + +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_len, + u8_t remove_object); + +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len); + +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidate, + int *candidate_count, + char fs_crammed); + +s32_t spiffs_gc_clean( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages); + +// --------------- + +s32_t spiffs_fd_find_new( + spiffs *fs, + spiffs_fd **fd, + const char *name); + +s32_t spiffs_fd_return( + spiffs *fs, + spiffs_file f); + +s32_t spiffs_fd_get( + spiffs *fs, + spiffs_file f, + spiffs_fd **fd); + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path); +#endif + +#if SPIFFS_CACHE +void spiffs_cache_init( + spiffs *fs); + +void spiffs_cache_drop_page( + spiffs *fs, + spiffs_page_ix pix); + +#if SPIFFS_CACHE_WR +spiffs_cache_page *spiffs_cache_page_allocate_by_fd( + spiffs *fs, + spiffs_fd *fd); + +void spiffs_cache_fd_release( + spiffs *fs, + spiffs_cache_page *cp); + +spiffs_cache_page *spiffs_cache_page_get_by_fd( + spiffs *fs, + spiffs_fd *fd); +#endif +#endif + +s32_t spiffs_lookup_consistency_check( + spiffs *fs, + u8_t check_all_objects); + +s32_t spiffs_page_consistency_check( + spiffs *fs); + +s32_t spiffs_object_index_consistency_check( + spiffs *fs); + +#endif /* SPIFFS_NUCLEUS_H_ */ diff --git a/components/spiffs/spiffs_vfs.c b/components/spiffs/spiffs_vfs.c new file mode 100644 index 0000000..0e52cf4 --- /dev/null +++ b/components/spiffs/spiffs_vfs.c @@ -0,0 +1,878 @@ +/* + * spiffs VFS operations + * + * Author: LoBo (loboris@gmail.com / https://github.com/loboris) + * + * Part of this code is copied from or inspired by LUA-RTOS_ESP32 project: + * + * https://github.com/whitecatboard/Lua-RTOS-ESP32 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + */ + + +#include + +#include +#include +#include +#include "esp_log.h" + +#include + +#include "esp_vfs.h" +#include "esp_attr.h" +#include + +#include +#include +#include +#include +#include "list.h" +#include +#include +#include "sdkconfig.h" + + +#ifdef PATH_MAX +#undef PATH_MAX +#endif +#define PATH_MAX MAXNAMLEN+8 + +#define SPIFFS_ERASE_SIZE 4096 + +int spiffs_is_registered = 0; +int spiffs_is_mounted = 0; + +QueueHandle_t spiffs_mutex = NULL; + +static int IRAM_ATTR vfs_spiffs_open(const char *path, int flags, int mode); +static ssize_t IRAM_ATTR vfs_spiffs_write(int fd, const void *data, size_t size); +static ssize_t IRAM_ATTR vfs_spiffs_read(int fd, void * dst, size_t size); +static int IRAM_ATTR vfs_spiffs_fstat(int fd, struct stat * st); +static int IRAM_ATTR vfs_spiffs_close(int fd); +static off_t IRAM_ATTR vfs_spiffs_lseek(int fd, off_t size, int mode); + +typedef struct { + DIR dir; + spiffs_DIR spiffs_dir; + char path[MAXNAMLEN + 1]; + struct dirent ent; + uint8_t read_mount; +} vfs_spiffs_dir_t; + +typedef struct { + spiffs_file spiffs_file; + char path[MAXNAMLEN + 1]; + uint8_t is_dir; +} vfs_spiffs_file_t; + +typedef struct { + time_t mtime; + time_t ctime; + time_t atime; + uint8_t spare[SPIFFS_OBJ_META_LEN - (sizeof(time_t)*3)]; +} spiffs_metadata_t; + +static spiffs fs; +static struct list files; + +static u8_t *my_spiffs_work_buf; +static u8_t *my_spiffs_fds; +static u8_t *my_spiffs_cache; + + +/* + * ######################################## + * file names/paths passed to the functions + * do not contain '/spiffs' prefix + * ######################################## + */ + +//---------------------------------------------------- +void spiffs_fs_stat(uint32_t *total, uint32_t *used) { + if (SPIFFS_info(&fs, total, used) != SPIFFS_OK) { + *total = 0; + *used = 0; + } +} + +/* + * Test if path corresponds to a directory. Return 0 if is not a directory, + * 1 if it's a directory. + * + */ +//----------------------------------- +static int is_dir(const char *path) { + spiffs_DIR d; + char npath[PATH_MAX + 1]; + int res = 0; + + struct spiffs_dirent e; + + // Add /. to path + strlcpy(npath, path, PATH_MAX); + if (strcmp(path,"/") != 0) { + strlcat(npath,"/.", PATH_MAX); + } else { + strlcat(npath,".", PATH_MAX); + } + + SPIFFS_opendir(&fs, "/", &d); + while (SPIFFS_readdir(&d, &e)) { + if (strncmp(npath, (const char *)e.name, strlen(npath)) == 0) { + res = 1; + break; + } + } + + SPIFFS_closedir(&d); + + return res; +} + +/* + * This function translate error codes from SPIFFS to errno error codes + * + */ +//------------------------------- +static int spiffs_result(int res) { + switch (res) { + case SPIFFS_OK: + case SPIFFS_ERR_END_OF_OBJECT: + return 0; + + case SPIFFS_ERR_NOT_FOUND: + case SPIFFS_ERR_CONFLICTING_NAME: + return ENOENT; + + case SPIFFS_ERR_NOT_WRITABLE: + case SPIFFS_ERR_NOT_READABLE: + return EACCES; + + case SPIFFS_ERR_FILE_EXISTS: + return EEXIST; + + default: + return res; + } +} + +//----------------------------------------------------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_getstat(spiffs_file fd, spiffs_stat *st, spiffs_metadata_t *metadata) { + int res = SPIFFS_fstat(&fs, fd, st); + if (res == SPIFFS_OK) { + // Get file's time information from metadata + memcpy(metadata, st->meta, sizeof(spiffs_metadata_t)); + } + return res; +} + +// ## path does not contain '/spiffs' prefix ! +//--------------------------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_open(const char *path, int flags, int mode) { + int fd, result = 0, exists = 0; + spiffs_stat stat; + spiffs_metadata_t meta; + + // Allocate new file + vfs_spiffs_file_t *file = calloc(1, sizeof(vfs_spiffs_file_t)); + if (!file) { + errno = ENOMEM; + return -1; + } + + // Add file to file list. List index is file descriptor. + int res = list_add(&files, file, &fd); + if (res) { + free(file); + errno = res; + return -1; + } + + // Check if file exists + if (SPIFFS_stat(&fs, path, &stat) == SPIFFS_OK) exists = 1; + + // Make a copy of path + strlcpy(file->path, path, MAXNAMLEN); + + // Open file + spiffs_flags spiffs_mode = 0; + + // Translate flags to SPIFFS flags + if (flags == O_RDONLY) + spiffs_mode |= SPIFFS_RDONLY; + + if (flags & O_WRONLY) + spiffs_mode |= SPIFFS_WRONLY; + + if (flags & O_RDWR) + spiffs_mode = SPIFFS_RDWR; + + if (flags & O_EXCL) + spiffs_mode |= SPIFFS_EXCL; + + if (flags & O_CREAT) + spiffs_mode |= SPIFFS_CREAT; + + if (flags & O_TRUNC) + spiffs_mode |= SPIFFS_TRUNC; + + if (is_dir(path)) { + char npath[PATH_MAX + 1]; + + // Add /. to path + strlcpy(npath, path, PATH_MAX); + if (strcmp(path,"/") != 0) { + strlcat(npath,"/.", PATH_MAX); + } else { + strlcat(npath,".", PATH_MAX); + } + + // Open SPIFFS file + file->spiffs_file = SPIFFS_open(&fs, npath, spiffs_mode, 0); + if (file->spiffs_file < 0) { + result = spiffs_result(fs.err_code); + } + + file->is_dir = 1; + } else { + // Open SPIFFS file + file->spiffs_file = SPIFFS_open(&fs, path, spiffs_mode, 0); + if (file->spiffs_file < 0) { + result = spiffs_result(fs.err_code); + } + } + + if (result != 0) { + list_remove(&files, fd, 1); + errno = result; + return -1; + } + + res = vfs_spiffs_getstat(file->spiffs_file, &stat, &meta); + if (res == SPIFFS_OK) { + // update file's time information + meta.atime = time(NULL); // Get the system time to access time + if (!exists) meta.ctime = meta.atime; + if (spiffs_mode != SPIFFS_RDONLY) meta.mtime = meta.atime; + SPIFFS_fupdate_meta(&fs, file->spiffs_file, &meta); + } + + return fd; +} + +//-------------------------------------------------------------------------------- +static ssize_t IRAM_ATTR vfs_spiffs_write(int fd, const void *data, size_t size) { + vfs_spiffs_file_t *file; + int res; + + res = list_get(&files, fd, (void **)&file); + if (res) { + errno = EBADF; + return -1; + } + + if (file->is_dir) { + errno = EBADF; + return -1; + } + + // Write SPIFFS file + res = SPIFFS_write(&fs, file->spiffs_file, (void *)data, size); + if (res >= 0) { + return res; + } else { + res = spiffs_result(fs.err_code); + if (res != 0) { + errno = res; + return -1; + } + } + + return -1; +} + +//------------------------------------------------------------------------- +static ssize_t IRAM_ATTR vfs_spiffs_read(int fd, void * dst, size_t size) { + vfs_spiffs_file_t *file; + int res; + + res = list_get(&files, fd, (void **)&file); + if (res) { + errno = EBADF; + return -1; + } + + if (file->is_dir) { + errno = EBADF; + return -1; + } + + // Read SPIFFS file + res = SPIFFS_read(&fs, file->spiffs_file, dst, size); + if (res >= 0) { + return res; + } else { + res = spiffs_result(fs.err_code); + if (res != 0) { + errno = res; + return -1; + } + + // EOF + return 0; + } + + return -1; +} + +//--------------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_fstat(int fd, struct stat * st) { + vfs_spiffs_file_t *file; + spiffs_stat stat; + int res; + spiffs_metadata_t meta; + + res = list_get(&files, fd, (void **)&file); + if (res) { + errno = EBADF; + return -1; + } + + // Set block size for this file system + st->st_blksize = CONFIG_SPIFFS_LOG_PAGE_SIZE; + + // Get file/directory statistics + res = vfs_spiffs_getstat(file->spiffs_file, &stat, &meta); + if (res == SPIFFS_OK) { + // Set file's time information from metadata + st->st_mtime = meta.mtime; + st->st_ctime = meta.ctime; + st->st_atime = meta.atime; + + st->st_size = stat.size; + + } else { + st->st_mtime = 0; + st->st_ctime = 0; + st->st_atime = 0; + st->st_size = 0; + errno = spiffs_result(fs.err_code); + //printf("SPIFFS_STAT: error %d\r\n", res); + return -1; + } + + // Test if it's a directory entry + if (file->is_dir) st->st_mode = S_IFDIR; + else st->st_mode = S_IFREG; + + return 0; +} + +//--------------------------------------------- +static int IRAM_ATTR vfs_spiffs_close(int fd) { + vfs_spiffs_file_t *file; + int res; + + res = list_get(&files, fd, (void **)&file); + if (res) { + errno = EBADF; + return -1; + } + + res = SPIFFS_close(&fs, file->spiffs_file); + if (res) { + res = spiffs_result(fs.err_code); + } + + if (res < 0) { + errno = res; + return -1; + } + + list_remove(&files, fd, 1); + + return 0; +} + +//--------------------------------------------------------------------- +static off_t IRAM_ATTR vfs_spiffs_lseek(int fd, off_t size, int mode) { + vfs_spiffs_file_t *file; + int res; + + res = list_get(&files, fd, (void **)&file); + if (res) { + errno = EBADF; + return -1; + } + + if (file->is_dir) { + errno = EBADF; + return -1; + } + + int whence = SPIFFS_SEEK_CUR; + + switch (mode) { + case SEEK_SET: whence = SPIFFS_SEEK_SET;break; + case SEEK_CUR: whence = SPIFFS_SEEK_CUR;break; + case SEEK_END: whence = SPIFFS_SEEK_END;break; + } + + res = SPIFFS_lseek(&fs, file->spiffs_file, size, whence); + if (res < 0) { + res = spiffs_result(fs.err_code); + errno = res; + return -1; + } + + return res; +} + +//------------------------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_stat(const char * path, struct stat * st) { + int fd; + int res; + fd = vfs_spiffs_open(path, 0, 0); + res = vfs_spiffs_fstat(fd, st); + vfs_spiffs_close(fd); + + return res; +} + +//-------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_unlink(const char *path) { + char npath[PATH_MAX + 1]; + + strlcpy(npath, path, PATH_MAX); + + if (is_dir(path)) { + // Check if directory is empty + int nument = 0; + sprintf(npath, "/spiffs"); + strlcat(npath, path, PATH_MAX); + + DIR *dir = opendir(npath); + if (dir) { + struct dirent *ent; + // Read directory entries + while ((ent = readdir(dir)) != NULL) { + nument++; + } + } + else { + errno = ENOTEMPTY; + return -1; + } + closedir(dir); + + if (nument > 0) { + // Directory not empty, cannot remove + errno = ENOTEMPTY; + return -1; + } + + strlcpy(npath, path, PATH_MAX); + // Add /. to path + if (strcmp(path,"/") != 0) { + strlcat(npath,"/.", PATH_MAX); + } + } + + // Open SPIFFS file + spiffs_file FP = SPIFFS_open(&fs, npath, SPIFFS_RDWR, 0); + if (FP < 0) { + errno = spiffs_result(fs.err_code); + return -1; + } + + // Remove SPIFSS file + if (SPIFFS_fremove(&fs, FP) < 0) { + errno = spiffs_result(fs.err_code); + SPIFFS_close(&fs, FP); + return -1; + } + + SPIFFS_close(&fs, FP); + + return 0; +} + +//------------------------------------------------------------------------ +static int IRAM_ATTR vfs_spiffs_rename(const char *src, const char *dst) { + if (SPIFFS_rename(&fs, src, dst) < 0) { + errno = spiffs_result(fs.err_code); + return -1; + } + + return 0; +} + +//------------------------------------------------ +static DIR* vfs_spiffs_opendir(const char* name) { + struct stat st; + + if (strcmp(name, "/") != 0) { + // Not on root + if (vfs_spiffs_stat(name, &st)) { + // Not found + errno = ENOENT; + return NULL; + } + if (!S_ISDIR(st.st_mode)) { + // Not a directory + errno = ENOTDIR; + return NULL; + } + } + + vfs_spiffs_dir_t *dir = calloc(1, sizeof(vfs_spiffs_dir_t)); + + if (!dir) { + errno = ENOMEM; + return NULL; + } + + if (!SPIFFS_opendir(&fs, name, &dir->spiffs_dir)) { + free(dir); + errno = spiffs_result(fs.err_code); + return NULL; + } + + strlcpy(dir->path, name, MAXNAMLEN); + + return (DIR *)dir; +} + +//--------------------------------------------------- +static struct dirent* vfs_spiffs_readdir(DIR* pdir) { + int res = 0, len = 0, entries = 0; + vfs_spiffs_dir_t* dir = (vfs_spiffs_dir_t*) pdir; + + struct spiffs_dirent e; + struct spiffs_dirent *pe = &e; + + struct dirent *ent = &dir->ent; + + char *fn; + + // Clear current dirent + memset(ent,0,sizeof(struct dirent)); + + // If this is the first call to readdir for pdir, and + // directory is the root path, return the mounted point if any + if (!dir->read_mount) { + if (strcmp(dir->path,"/") == 0) { + strlcpy(ent->d_name, "/spiffs", PATH_MAX); + ent->d_type = DT_DIR; + dir->read_mount = 1; + + return ent; + } + + dir->read_mount = 1; + } + + // Search for next entry + for(;;) { + // Read directory + pe = SPIFFS_readdir(&dir->spiffs_dir, pe); + if (!pe) { + res = spiffs_result(fs.err_code); + errno = res; + break; + } + + // Break condition + if (pe->name[0] == 0) break; + + // Get name and length + fn = (char *)pe->name; + len = strlen(fn); + + // Get entry type and size + ent->d_type = DT_REG; + + if (len >= 2) { + if (fn[len - 1] == '.') { + if (fn[len - 2] == '/') { + ent->d_type = DT_DIR; + + fn[len - 2] = '\0'; + + len = strlen(fn); + + // Skip root dir + if (len == 0) { + continue; + } + } + } + } + + // Skip entries not belonged to path + if (strncmp(fn, dir->path, strlen(dir->path)) != 0) { + continue; + } + + if (strlen(dir->path) > 1) { + if (*(fn + strlen(dir->path)) != '/') { + continue; + } + } + + // Skip root directory + fn = fn + strlen(dir->path); + len = strlen(fn); + if (len == 0) { + continue; + } + + // Skip initial / + if (len > 1) { + if (*fn == '/') { + fn = fn + 1; + len--; + } + } + + // Skip subdirectories + if (strchr(fn,'/')) { + continue; + } + + //ent->d_fsize = pe->size; + + strlcpy(ent->d_name, fn, MAXNAMLEN); + + entries++; + + break; + } + + if (entries > 0) { + return ent; + } else { + return NULL; + } +} + +//-------------------------------------------------- +static int IRAM_ATTR vfs_piffs_closedir(DIR* pdir) { + vfs_spiffs_dir_t* dir = (vfs_spiffs_dir_t*) pdir; + int res; + + if (!pdir) { + errno = EBADF; + return -1; + } + + if ((res = SPIFFS_closedir(&dir->spiffs_dir)) < 0) { + errno = spiffs_result(fs.err_code);; + return -1; + } + + free(dir); + + return 0; +} + +//-------------------------------------------------------------------- +static int IRAM_ATTR vfs_spiffs_mkdir(const char *path, mode_t mode) { + char npath[PATH_MAX + 1]; + int res; + + // Add /. to path + strlcpy(npath, path, PATH_MAX); + if ((strcmp(path,"/") != 0) && (strcmp(path,"/.") != 0)) { + strlcat(npath,"/.", PATH_MAX); + } + + spiffs_file fd = SPIFFS_open(&fs, npath, SPIFFS_CREAT, 0); + if (fd < 0) { + res = spiffs_result(fs.err_code); + errno = res; + return -1; + } + + if (SPIFFS_close(&fs, fd) < 0) { + res = spiffs_result(fs.err_code); + errno = res; + return -1; + } + + spiffs_metadata_t meta; + meta.atime = time(NULL); // Get the system time to access time + meta.ctime = meta.atime; + meta.mtime = meta.atime; + SPIFFS_update_meta(&fs, npath, &meta); + + return 0; +} + + +static const char tag[] = "[SPIFFS]"; + +//================== +int spiffs_mount() { + + if (!spiffs_is_registered) return 0; + if (spiffs_is_mounted) return 1; + + spiffs_config cfg; + int res = 0; + int retries = 0; + int err = 0; + + ESP_LOGI(tag, "Mounting SPIFFS files system"); + + cfg.phys_addr = CONFIG_SPIFFS_BASE_ADDR; + cfg.phys_size = CONFIG_SPIFFS_SIZE; + cfg.phys_erase_block = SPIFFS_ERASE_SIZE; + cfg.log_page_size = CONFIG_SPIFFS_LOG_PAGE_SIZE; + cfg.log_block_size = CONFIG_SPIFFS_LOG_BLOCK_SIZE; + + cfg.hal_read_f = (spiffs_read)low_spiffs_read; + cfg.hal_write_f = (spiffs_write)low_spiffs_write; + cfg.hal_erase_f = (spiffs_erase)low_spiffs_erase; + + my_spiffs_work_buf = malloc(cfg.log_page_size * 8); + if (!my_spiffs_work_buf) { + err = 1; + goto err_exit; + } + + int fds_len = sizeof(spiffs_fd) * SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + my_spiffs_fds = malloc(fds_len); + if (!my_spiffs_fds) { + free(my_spiffs_work_buf); + err = 2; + goto err_exit; + } + + int cache_len = cfg.log_page_size * SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + my_spiffs_cache = malloc(cache_len); + if (!my_spiffs_cache) { + free(my_spiffs_work_buf); + free(my_spiffs_fds); + err = 3; + goto err_exit; + } + + ESP_LOGI(tag, "Start address: 0x%x; Size %d KB", cfg.phys_addr, cfg.phys_size / 1024); + ESP_LOGI(tag, " Work buffer: %d B", cfg.log_page_size * 8); + ESP_LOGI(tag, " FDS buffer: %d B", sizeof(spiffs_fd) * SPIFFS_TEMPORAL_CACHE_HIT_SCORE); + ESP_LOGI(tag, " Cache size: %d B", cfg.log_page_size * SPIFFS_TEMPORAL_CACHE_HIT_SCORE); + while (retries < 2) { + res = SPIFFS_mount( + &fs, &cfg, my_spiffs_work_buf, my_spiffs_fds, + fds_len, my_spiffs_cache, cache_len, NULL + ); + + if (res < 0) { + if (fs.err_code == SPIFFS_ERR_NOT_A_FS) { + ESP_LOGW(tag, "No file system detected, formating..."); + SPIFFS_unmount(&fs); + res = SPIFFS_format(&fs); + if (res < 0) { + free(my_spiffs_work_buf); + free(my_spiffs_fds); + free(my_spiffs_cache); + ESP_LOGE(tag, "Format error"); + goto exit; + } + } + else { + free(my_spiffs_work_buf); + free(my_spiffs_fds); + free(my_spiffs_cache); + ESP_LOGE(tag, "Error mounting fs (%d)", res); + goto exit; + } + } + else break; + retries++; + } + + if (retries > 1) { + free(my_spiffs_work_buf); + free(my_spiffs_fds); + free(my_spiffs_cache); + ESP_LOGE(tag, "Can't mount"); + goto exit; + } + + list_init(&files, 0); + + ESP_LOGI(tag, "Mounted"); + + spiffs_is_mounted = 1; + return 1; + +err_exit: + ESP_LOGE(tag, "Error allocating fs structures (%d)", err); +exit: + esp_vfs_unregister("/spiffs"); + spiffs_is_registered = 0; + return 0; +} + +//========================== +void vfs_spiffs_register() { + + if (spiffs_is_registered) return; + + if (spiffs_mutex == NULL) { + spiffs_mutex = xSemaphoreCreateMutex(); + if (spiffs_mutex == NULL) { + ESP_LOGE(tag, "Error creating SPIFFS mutex"); + return; + } + } + + esp_vfs_t vfs = { + //.fd_offset = 0, // not available in latest esp-idf + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &vfs_spiffs_write, + .open = &vfs_spiffs_open, + .fstat = &vfs_spiffs_fstat, + .close = &vfs_spiffs_close, + .read = &vfs_spiffs_read, + .lseek = &vfs_spiffs_lseek, + .stat = &vfs_spiffs_stat, + .link = NULL, + .unlink = &vfs_spiffs_unlink, + .rename = &vfs_spiffs_rename, + .mkdir = &vfs_spiffs_mkdir, + .opendir = &vfs_spiffs_opendir, + .readdir = &vfs_spiffs_readdir, + .closedir = &vfs_piffs_closedir, + }; + + ESP_LOGI(tag, "Registering SPIFFS file system"); + esp_err_t res = esp_vfs_register(SPIFFS_BASE_PATH, &vfs, NULL); + if (res != ESP_OK) { + ESP_LOGE(tag, "Error, SPIFFS file system not registered"); + return; + } + spiffs_is_registered = 1; + + spiffs_mount(); +} + +//============================= +int spiffs_unmount(int unreg) { + + if (!spiffs_is_mounted) return 0; + + SPIFFS_unmount(&fs); + spiffs_is_mounted = 0; + + if (unreg) { + esp_vfs_unregister("/spiffs"); + spiffs_is_registered = 0; + } + return 1; +} diff --git a/components/spiffs/spiffs_vfs.h b/components/spiffs/spiffs_vfs.h new file mode 100644 index 0000000..1c627c3 --- /dev/null +++ b/components/spiffs/spiffs_vfs.h @@ -0,0 +1,23 @@ +/* + * spiffs VFS public function + * + * Author: LoBo (loboris@gmail.com / https://github.com/loboris) + * + * Part of this code is copied from or inspired by LUA-RTOS_ESP32 project: + * + * https://github.com/whitecatboard/Lua-RTOS-ESP32 + * IBEROXARXA SERVICIOS INTEGRALES, S.L. & CSS IBÉRICA, S.L. + * Jaume Olivé (jolive@iberoxarxa.com / jolive@whitecatboard.org) + * + */ + +#define SPIFFS_BASE_PATH "/spiffs" + + +int spiffs_is_registered; +int spiffs_is_mounted; + +void vfs_spiffs_register(); +int spiffs_mount(); +int spiffs_unmount(int unreg); +void spiffs_fs_stat(uint32_t *total, uint32_t *used); diff --git a/components/spiffs_image/Makefile.projbuild b/components/spiffs_image/Makefile.projbuild new file mode 100644 index 0000000..0ed7f7f --- /dev/null +++ b/components/spiffs_image/Makefile.projbuild @@ -0,0 +1,26 @@ +SPIFFS_IMAGE_COMPONENT_PATH := $(COMPONENT_PATH) +ifeq ($(OS),Windows_NT) + MKSPIFFS_BIN="mkspiffs.exe" +else + MKSPIFFS_BIN="mkspiffs" +endif + +.PHONY: flashfs +.PHONY: makefs +.PHONY: copyfs + +flashfs: $(SDKCONFIG_MAKEFILE) mkspiffs + @echo "Making spiffs image ..." + @echo "$(ESPTOOLPY_WRITE_FLASH)" + $(MKSPIFFS_COMPONENT_PATH)/../mkspiffs/src/$(MKSPIFFS_BIN) -c $(SPIFFS_IMAGE_COMPONENT_PATH)/image -b $(CONFIG_SPIFFS_LOG_BLOCK_SIZE) -p $(CONFIG_SPIFFS_LOG_PAGE_SIZE) -s $(CONFIG_SPIFFS_SIZE) $(BUILD_DIR_BASE)/spiffs_image.img + $(ESPTOOLPY_WRITE_FLASH) $(CONFIG_SPIFFS_BASE_ADDR) $(BUILD_DIR_BASE)/spiffs_image.img + +makefs: $(SDKCONFIG_MAKEFILE) mkspiffs + @echo "Making spiffs image ..." + @echo "$(ESPTOOLPY_WRITE_FLASH)" + $(MKSPIFFS_COMPONENT_PATH)/../mkspiffs/src/$(MKSPIFFS_BIN) -c $(SPIFFS_IMAGE_COMPONENT_PATH)/image -b $(CONFIG_SPIFFS_LOG_BLOCK_SIZE) -p $(CONFIG_SPIFFS_LOG_PAGE_SIZE) -s $(CONFIG_SPIFFS_SIZE) $(BUILD_DIR_BASE)/spiffs_image.img + +copyfs: + @echo "Flashing spiffs image ..." + @echo "$(ESPTOOLPY_WRITE_FLASH)" + $(ESPTOOLPY_WRITE_FLASH) $(CONFIG_SPIFFS_BASE_ADDR) $(SPIFFS_IMAGE_COMPONENT_PATH)/spiffs_image.img diff --git a/components/spiffs_image/component.mk b/components/spiffs_image/component.mk new file mode 100644 index 0000000..12f5e46 --- /dev/null +++ b/components/spiffs_image/component.mk @@ -0,0 +1,6 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := +COMPONENT_ADD_INCLUDEDIRS := diff --git a/components/spiffs_image/image/fonts/BigFont.fon b/components/spiffs_image/image/fonts/BigFont.fon new file mode 100644 index 0000000000000000000000000000000000000000..c7b3c4811036ff0e7db0b21c987fab21a5904a92 GIT binary patch literal 3052 zcma)8y^h>A5T=&EaSa^Ay-sKImVSgS71)5UbQ!pEXICy06)CPb;8ePF>0%!urArnm z^8hYf@DW(55bS(Ia!6_Sl9&UCBhC-!U)t@SzY{;Pl+YuC5-Lj2p0Ku0aB+BC)#ZWU z;rqk)ge`HB*xYVz^<<}~w@+_v(9`Da=B=GlgLTGV4 z5@?~f`t$YFh0b3G{h2wEtu4Qv?;-0IZ3HFc*fNqOa8 z&97$KpM1Ld*o=h%NHY@XphLozv~&F5EJj?WbMEt#Bwx-q;sTBt(Zl{|J7S1eUOJfG z+?mNtH(zeP*xvU0-t@ZoPZrD30^|a0p=)mj)33BczFJ!8(n$v!KsAH*d_QdR(qH&X z-}&gF7XVp2wUdFh1lHr$yHk1TlRuyb@RD{wwcpMrE+{AwAW!)I=szTEPq{-JPUM+R zKIsWMq88;?p7s=(HTGZh)H{)5l@o)^C}>0dQN1k|)fT<-&ZHBGl{Vs$PKW(nSMmeR zSdAkfOiw8~c+MG8tb@uKkWTp)8BS0mOwzP$;2A*m&3LU3g)_)Tt@(+Wp-TN&XlJRi z57tNJqW zZ>+IL&}VaRUM3#akN%(xEEYvydZwS`FWXY%C%wK~$lse;r#^`Fc$WX1%j#e0AE9AM z>|WHvvOll?zWOUwWPR59s2N#eHNMN!gtuNfIkJBX$B&5Lk*rsPD*0W?;QNC7q?00F z>Lck*bx`l8j_CSb?Yyn&mh(@AG>=c7rT%xKLvO75zQ=*$`wtOrapnFZd8ll!N&r0oTbv6!P0$wRA zLPBQ+Gh7DciKXbzQ{c4!*LwMmg?>_kg*uD%%g=OLpElPmds!(7%khudW8arN&oCtz zkV|B>&H&=GfA*fidI4xwIo{*%4e>jhAAASyKbV6arSFDrkw1KmgcQhL?Gxm)&L@^< zztUdo@0`Bw|3XiLAWnc~{};M9n9*ufqjm;3pYzVK$+5$Pe*f){ Fe*kS73)%nx literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/DejaVuSans12.fon b/components/spiffs_image/image/fonts/DejaVuSans12.fon new file mode 100644 index 0000000000000000000000000000000000000000..36e76bfcb5c404895f976eba326fa4b26adf7b65 GIT binary patch literal 1166 zcmYjRO>7%Q6rQ&;yF0sUV4V7}L8!Ht4KaVM1BEy=6lb$ZV^e6FHZ4CXv7Hhqv`v}@ z>c*vAFRtrUp=xu>jUq*=DsD(ziiDG(9x8~qa5o%#3K9o5GBL9gRYH2w&NuIS-+S}k z(;@?*sE!aKF#t`H|Me`u7~~(lXC+EOZ;M>9?XCEb4T>Sr*n>OuzwS=^r~`Bj89962 z8b0ISvePl|InXrF6E+LUkxctS5p@D!!1Xx72FRz<_K^3yIL^1FGw2176;L|H9^QNr zt19R$df;z~pqD6*A@!FP4E(d3f&`?t2Z)YH*s|Sf0u&AC#dxk_jug5;=HO52uyW%S zPy>g_qCSl_Ulp`D)|X676!r>W<`*-2R_SX3Y|`Ekn<%_4K##e9-8@q07NE&|=Fn79 zfMW7Iw?fZ0i06L$JwN>h0R_mf`v|B&)_XBkK*u}l^_}=lnPSjesI^~Q)(WaxsJ+E& zM9QpVF%nIIVZal+ly(?g+F_kMdOAheCDaF0L)0{F+jcHHB}XLNG;`b<{Q~ru{88bg z8$3~aq=(EzcC|4eTw65CtvMKULT$#LT4mk=LF=s$x{0P0;R{5sAE5BAb zBvA5tP|SD9;q9W66Pq;Z9R}3^?Xiook$_4cJsp|46?6O}f?6~a1wNXi+=bMwXsRR; z%0`7IuT^Ls;~UnZshbun$4Duf2%+O(@SQ8i=fj=WozW-j)i8Qb2;8sUUQu<+e_tSf zKHTqfxetWg7GZsarM5l<$&loScfTJK+f;QOD|~|Af&9nl*hdhI&a|EJkOD|)W)YS^ zadTKvfzBnyE8VF)Qgom`-TlDKt=A{8tb#g|qaC%A!a>-Hb(1g1$0DRBd8M?&n?%9h z?mgtX?k6~izL3ma>+U;P|MaoeeKS{Kr^HZx_C5nCgz&)VvCjjy>Qig+Mtm`P8iCGd zRy%_=6^tJy^Ihh`pmkQ{xyR$p)RZ_E195L!B)S*RUEBAqkIwxsEnzd5s38ATt$OE! z2z4g@jNXZ6foMo}|8d>K7ec7QMY~g9Vyd@N3ss&C%%2j(I z9Zy}vRNtmWOiEdHjyDF$4cG0j7eVHKjCSf|%Q58=Ku993jj#A6&=u;0%CW>{60{wi PyU`jM9vYvVIy?IxROtF6 literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/DejaVuSans18.fon b/components/spiffs_image/image/fonts/DejaVuSans18.fon new file mode 100644 index 0000000000000000000000000000000000000000..0d609fb733721b76367eefd1052953c618ee8372 GIT binary patch literal 1836 zcmZuxTW=dh6rPz~@A@*{b&{8j61*v;+$rlAT$gU+778s;=!H8JYS-YF;>H0lmbTDz#|n2r*==jy2~AVD_5GSxwfg_=1#!MPxBUe#D@Z`Oo43k z2_Y!ufp8p{Vkn4IFk2M!iNJo@*1P6#K}yS=Y_rqIHge5gr;r8(U>Z8}-^s`Yn9-pEXRtCe zSjDNPo6bqZc$6VVxL2BO=eMlfsMT$UF58d6kk<>%RWgfV*^mIikX&+lyOEJc2d>X#wOqJjn-Xbh7EZVDUY>L<8$T(1 zhEFPTk$e;%jC*rYL zXFM@DozwE%oU!Sg5S1Y6`(@(hALkv*Qn6|9mvNGnfkOWzIK=@+^~E7?0K&D>RJp*= zF_Y5V8ajH4<&eO;|H16#eovHw$p4$mI>E5OY448wMfZEV7?o=7_!%gLp`59uR%=?O zrkn5c%0dz?dEXlIR9RIVDW z%v|BTh?QFcurBj+6A>nywZB>gVH5oe#N% i)AjxsR#mocnRJ~=pVvG*BjX$)oYDgw{hHV5s0}Q<}Wx;y#PfhETHX!V7>amfN5;tCm|X`K)r#d%N~v{6%e#T4u_mM zJUo~0oHK9?;N(L9Q2HS8sso3twKV$>VcZ+usBdr7^(j)RdLy(%1J}G@&dymD4MgIk zNNwNtgL8p)t)f3nhL3u308~YG{{7Fl*vWUK>QnPnX?5(h?U;%O=SuJqGEybS>XfBv zn~6xbYhqlrV=)028tt_UupZUgWGRZr}% zb*@{c)y$I{a`80rxW{e&Q8IYM8ya0&8xPjTm(GiZIIlidylV%I;BoiziSd6&XN(CU0=%LqO4HQnh={5uCC!_6SvpO1eXmcl^m#wg**G~=rLvv~9SI5t2Wzv> z7P}MKTh%_K3{pXKK;1({4D42aVuyg|k7@ks2*UXs42Ot9%eU?9bEI4)lIStGURLWY zZO|ZUvtWFF+1i|3!is}%?6x^I{DWN`*x?Z>B)KWzLd?`?52 zMa?8_UcQ#--kD;5!7r_$bSR<0TL&C(263@JTI z=6aT!`(g6dHlR4~)aDk<7q}c(QJ}Ap{1U#tIEFry6)!_<+N@|)x z1}7gaNlr)5gE^ucAjFxPDz+$MNDpZfkeaK6X5oB~s}ev$hN@n9D!HCTfe^m$GfETy zF5sMZE^NY0k`aSPD+yyzhaS1k)4IK9;mf<8 zv-^;`{a}A;^Cu9So?ke&0H%F=b04Dc6+8pu(q#nl5Lf97E> z&69{guZjT7xT+H)p+(Ub|6Sl0{Z&jt7{TuS*9be-=OSt!?8mp@>rSPLo|$iRRuj!p zeFkQoCNyTTh=Vwa(^x;E;%_*W$_Jw4z!Ww4rps$b49{dHd*RLFH;=DRxVF|s^?K7!3m5L}cB5N2 z{dzs@ar2B5%eUbJv^-bYm~U@)_bjag_!g-gamM@~jQBpI3}Osg@vIZ>u;u+|+(q>S zSqW?>k7nkV+~OwFlc2x7A9vHB1!xX)WG^>hV0U}pK`eoEM&HNKE|d;FzWaXv?K`uq zZup6rh3^#r-_bZ_4%>0ffqMt$4%<8}jH7LPR&B zwP1DS0z1DldNrLcs|>ScCaI1&{J^2@Fxogs(>uBY>jk70S0S8%HG0+e1Cf?>?T3X< z=7efczZRqukQ2iXMoFRCk6aX0oaRLN&~#yxQdPqNjcYg#?%R)x+F|c4HC*X%gH=B% zGQBownqrDCbQA*9KBqt3q28rL@{UzMBQ6P1CDmEgU0AqA|4P+!$Akw0`KQ1JJdv(0 zoM*#j;wSPwiWx7tZK-aBi*Ik={dwU)=Q`JWcEdNjnSh#$zFqG%*r75h%jNrnYNv1n ze85We3*1MfD*@QYx0Cxao!u=v>1CPR+x{gJc%WRsqQrILQY2P^=Bd8a7f7i}N-~V2 z$XZlz?|Pm&mf?hL{)_L{uu0zBwjrMIlAo;hepO_W-B^1wxxZ+fiwmfuI^x$wfaiEt z_8T&I%<^Cj#GY9v9;n|sRsB6xYwe}xjk4}ZbxW9zh*#JBX~%$})ok_x8!3Ci#^Ofa z&w5ul)oGo*e~4a0t9o+d%?IslPYfp!yI3e6I_24(Dy-SfL)AnjamR9=y}S2iie+2* z#?p=lVCMf=p|JK#tW@?6ZP?7{z3Y;5u1MteYpH-t>N@BT@VQR*vR66`L(aYj3?0hf z6;c=Ru_twbg;!X)Or$^El@uEpc(CdUGwozQC{Fz-_JDs%q`1#i=3K@)kG)igC285j>l zA$Z{TPK|O=3QX)%g#%G@w*Ln*bd*ZE(4JR1#Y*WEb#(ba&EKsj|Hh| p`B+sQiBIp`(>t_Wa=0=i2eF>R>|lQ^G*a1TpLz1R3m0E}`9IO;U!4E| literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/DotMatrix_M.fon b/components/spiffs_image/image/fonts/DotMatrix_M.fon new file mode 100644 index 0000000000000000000000000000000000000000..a932f51b5f8be9f3d467b3f078b0e4804583f4a1 GIT binary patch literal 4192 zcmbVPF>>QD45S-Z)=a9@_XJgbL(0s3AX1Y`zBKm$Rep~uzwaO{pezhhcFvxmU=Uh> zz!IciUVnX*AAJtP1J!UFXt={5CVEHXcLE3VKB4UPg@$89=rmL+@62GA?Xc6K(ldH+QqFUB_As!4|b( z)l)M2*j~_za(=CDT6MR=dWYj6HExZ@5UW0~gfjryZNVh43Zr>a?_eIX=&Z}OtTDVw zjD~{M@>nnfU*=KyUBj(Jr?#AvZEd>87^R(M!=?PTG`m3)BX^oD{oMxGE-PUw^hTv?$$pjf^3v6T!IsZvx9mCc;ohLcLz#8KY=9OgG*OvKUniRF?H)B~m;f!tJ)^Zl&RlgRUc2oG<;??T6s>}MwryO;}&GUS# zd4$W=Lp^~PV*nzsAhXwKiy3|+#$X)UJ!73`cHf!zZhv+gvUUj;=Wb~`#jN&EV#Es@ zKO=b62J)YeyS3p)rz^x+_z0F6X17t@N#3iG0gU4^%6$=gk1OVVl6@JM)9!Kpa<5^( zhO2j4zo%AkHAdc{8!nwt&@o?FmVJ!dH+K&9tDJJ&XYzp9y~c=|J!bhiTfZ4_4CuD4 zF6_A3BWr35nfgioA@DwCnJZB{o0%2S7J1}($e!QaC#}aT=XmAS{+sF+cPL^p{_4Le zMudD9@9sadnBNx9YWd9>usq;~8G^{R+y{ecxHE9Y@J7v+vD*EJW3ZkO_5~wbFQOd? s=6a9f+c|j1ofR6-8S`{sj<;Dx;rDzGp{HQ?aO_$B{r>jx`Ss`TKMmP7>;M1& literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/Grotesk24x48.fon b/components/spiffs_image/image/fonts/Grotesk24x48.fon new file mode 100644 index 0000000000000000000000000000000000000000..ee59c364b2a424a214db2027a935dfc57906fe3e GIT binary patch literal 13692 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 literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/SmallFont.fon b/components/spiffs_image/image/fonts/SmallFont.fon new file mode 100644 index 0000000000000000000000000000000000000000..bf3f5f656f3137b198adca3711ea5274629ec9c0 GIT binary patch literal 1152 zcmY*YEpyy35ETdzC@?e#SZF9vU=Sb>uyChS5YU+zbpkrAK!HGkp`l?wK|z6FVPSqi ze!zi(g8>1C1_%0fWnZsv=6tKCr?(%n{TsS>|A7pSKz1Vkwz}@r$;lYX<;M`ZvZ#p5 zFX`UCXH}9&E@l3NFvd|C!kNNDHSp0CyXTZWTa0YF zZIA6=51?N{pW3bh>1zkZ>7KGy8`ZOEdMxGln3X3&ahabrUYHXzFB<1*1ZoZ76~?bD zNg`W@8posRdmhK{698uaFQ8F8Za{^OU=U;hFv&f#PL literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/Ubuntu.fon b/components/spiffs_image/image/fonts/Ubuntu.fon new file mode 100644 index 0000000000000000000000000000000000000000..fcc4b5b790989fb55416fcda75de8e969ebedece GIT binary patch literal 9132 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 GFaH6-J@Hxq literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/fonts/ocrfont.c b/components/spiffs_image/image/fonts/ocrfont.c new file mode 100644 index 0000000..1104238 --- /dev/null +++ b/components/spiffs_image/image/fonts/ocrfont.c @@ -0,0 +1,119 @@ +// OCR_A_Extended_M.c +// Font type : Full (95 characters) +// Font size : 16x24 pixels +// Memory usage : 4564 bytes + +#if defined(__AVR__) + #include + #define fontdatatype const uint8_t +#elif defined(__PIC32MX__) + #define PROGMEM + #define fontdatatype const unsigned char +#elif defined(__arm__) + #define PROGMEM + #define fontdatatype const unsigned char +#endif + +fontdatatype OCR_A_Extended_M[4564] PROGMEM={ +0x10,0x18,0x20,0x5F, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // +0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ! +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x1E,0x78,0x1E,0x78,0x0E,0x70,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // " +0x00,0x00,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x1F,0xF8,0x1F,0xF8,0x06,0x60,0x06,0x60,0x06,0x60,0x1F,0xF8,0x1F,0xF8,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // # +0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x1F,0xF8,0x1F,0xF8,0x18,0x00,0x18,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x18,0x00,0x18,0x1F,0xF8,0x1F,0xF8,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // $ +0x00,0x00,0x1C,0x00,0x1C,0x18,0x1C,0x18,0x00,0x30,0x00,0x30,0x00,0x60,0x00,0xE0,0x00,0xC0,0x01,0x80,0x01,0x80,0x03,0x00,0x07,0x00,0x06,0x00,0x0C,0x00,0x0C,0x00,0x18,0x38,0x18,0x38,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // % +0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xC0,0x0F,0xC0,0x18,0x60,0x18,0x60,0x18,0x60,0x18,0x60,0x0C,0xC0,0x07,0x80,0x07,0x00,0x0F,0x80,0x1D,0xC8,0x18,0xF8,0x18,0x70,0x18,0xF0,0x0F,0xD8,0x0F,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // & +0x00,0x00,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x80,0x03,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ' +0x00,0x00,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x03,0x80,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x80,0x01,0x80,0x00,0xC0,0x00,0x60,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ( +0x00,0x00,0x0C,0x00,0x06,0x00,0x03,0x00,0x01,0x80,0x01,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x00,0xC0,0x01,0xC0,0x01,0x80,0x03,0x00,0x06,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ) +0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x19,0x88,0x19,0x98,0x0F,0xF0,0x07,0xE0,0x03,0xC0,0x03,0xC0,0x07,0xE0,0x0F,0xF0,0x1D,0xB8,0x11,0x98,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // * +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x1F,0xF8,0x1F,0xF8,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x07,0xE0,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // , +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // - +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // . +0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x30,0x00,0x30,0x00,0x60,0x00,0x60,0x00,0xC0,0x00,0xC0,0x01,0x80,0x01,0x80,0x03,0x00,0x03,0x00,0x06,0x00,0x06,0x00,0x0C,0x00,0x0C,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // / + +0x00,0x00,0x0F,0xF0,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0 +0x00,0x00,0x1F,0x80,0x1F,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x98,0x01,0x98,0x01,0x98,0x01,0x98,0x01,0x98,0x01,0x98,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 1 +0x00,0x00,0x1F,0xF0,0x1F,0xF8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x0F,0xF8,0x1F,0xF0,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 2 +0x00,0x00,0x1F,0xF8,0x1F,0xFC,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x07,0xF8,0x07,0xF8,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x1F,0xFC,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 3 +0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x1F,0xF8,0x1F,0xF8,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 4 +0x00,0x00,0x07,0xF8,0x07,0xF8,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x07,0xF0,0x07,0xF8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x18,0x18,0x1F,0xF8,0x07,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 5 +0x00,0x00,0x1C,0x00,0x1C,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xF8,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 6 +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x18,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 7 +0x00,0x00,0x07,0xE0,0x07,0xE0,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x0F,0xF0,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 8 +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x1F,0xF8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x38,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 9 +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // : +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x07,0xE0,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ; +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x78,0x00,0xE0,0x01,0xC0,0x07,0x00,0x0E,0x00,0x1C,0x00,0x0E,0x00,0x07,0x00,0x01,0xC0,0x00,0xE0,0x00,0x78,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // < +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // = +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x1E,0x00,0x07,0x00,0x03,0x80,0x00,0xE0,0x00,0x70,0x00,0x38,0x00,0x70,0x00,0xE0,0x03,0x80,0x07,0x00,0x1E,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // > +0x00,0x00,0x00,0x20,0x00,0x60,0x01,0xF0,0x03,0x98,0x07,0x18,0x0E,0x18,0x18,0x70,0x10,0xE0,0x01,0xC0,0x03,0x80,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ? + +0x00,0x00,0x07,0xE0,0x0F,0xF0,0x18,0x18,0x18,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x0F,0x98,0x0F,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x0F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // @ +0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x06,0x60,0x06,0x60,0x06,0x60,0x06,0x60,0x0C,0x30,0x0F,0xF0,0x0F,0xF0,0x0C,0x30,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // A +0x00,0x00,0x1F,0xF0,0x1F,0xF8,0x18,0x1C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x1C,0x1F,0xF8,0x1F,0xF8,0x18,0x1C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x1C,0x1F,0xF8,0x1F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // B +0x00,0x00,0x01,0xFC,0x03,0xFC,0x03,0x00,0x06,0x00,0x06,0x00,0x0C,0x00,0x0C,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1C,0x00,0x0C,0x00,0x0E,0x00,0x06,0x00,0x07,0x00,0x03,0xFC,0x01,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // C +0x00,0x00,0x1F,0xC0,0x1F,0xE0,0x06,0x70,0x06,0x30,0x06,0x30,0x06,0x18,0x06,0x18,0x06,0x0C,0x06,0x0C,0x06,0x0C,0x06,0x0C,0x06,0x1C,0x06,0x18,0x06,0x38,0x06,0x30,0x06,0x70,0x1F,0xE0,0x1F,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // D +0x00,0x00,0x1F,0xFC,0x1F,0xFC,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xC0,0x1F,0xC0,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xFC,0x1F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // E +0x00,0x00,0x1F,0xFC,0x1F,0xFC,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xF0,0x1F,0xF0,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // F +0x00,0x00,0x01,0xF8,0x03,0xF8,0x03,0x00,0x06,0x00,0x0E,0x00,0x0C,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0xF8,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0F,0xF8,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // G +0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // H +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // I +0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x0C,0x18,0x07,0xF0,0x03,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // J +0x00,0x00,0x18,0x0C,0x18,0x18,0x18,0x30,0x18,0x60,0x18,0xC0,0x19,0x80,0x1B,0x00,0x1E,0x00,0x1C,0x00,0x1E,0x00,0x1F,0x00,0x1B,0x80,0x19,0xC0,0x18,0xE0,0x18,0x70,0x18,0x30,0x18,0x18,0x18,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // K +0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // L +0x00,0x00,0x1C,0x38,0x1C,0x38,0x1E,0x78,0x1E,0xF8,0x1B,0xD8,0x19,0x98,0x19,0x98,0x19,0x98,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // M +0x00,0x00,0x1C,0x18,0x1C,0x18,0x1E,0x18,0x1E,0x18,0x1E,0x18,0x1B,0x18,0x1B,0x18,0x1B,0x18,0x19,0x98,0x19,0x98,0x18,0xD8,0x18,0xD8,0x18,0xD8,0x18,0x78,0x18,0x78,0x18,0x78,0x18,0x38,0x18,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // N +0x00,0x00,0x03,0xC0,0x03,0xC0,0x06,0x60,0x06,0x60,0x0E,0x70,0x0C,0x30,0x0C,0x30,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x30,0x0C,0x30,0x0E,0x70,0x06,0x60,0x06,0x60,0x03,0xC0,0x01,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // O + +0x00,0x00,0x1F,0xF0,0x1F,0xF0,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF0,0x1F,0xF0,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // P +0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0xD8,0x03,0x98,0x07,0x18,0x0C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x19,0x98,0x19,0x98,0x19,0xF0,0x18,0xE0,0x19,0xC0,0x1B,0xE0,0x1F,0x78,0x0E,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Q +0x00,0x00,0x1F,0xF0,0x1F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x1F,0xF0,0x19,0x80,0x19,0x80,0x18,0xC0,0x18,0xC0,0x18,0x60,0x18,0x60,0x18,0x30,0x18,0x30,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // R +0x00,0x00,0x0F,0xF0,0x1F,0xF8,0x18,0x18,0x18,0x18,0x0C,0x00,0x06,0x00,0x06,0x00,0x03,0x00,0x01,0x80,0x01,0x80,0x00,0xC0,0x00,0x60,0x00,0x60,0x00,0x30,0x18,0x18,0x18,0x18,0x1F,0xF8,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // S +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x19,0x98,0x19,0x98,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // T +0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // U +0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x30,0x0C,0x30,0x0E,0x70,0x06,0x60,0x06,0x60,0x07,0xE0,0x03,0xC0,0x03,0xC0,0x03,0xC0,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // V +0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x19,0x98,0x0F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // W +0x00,0x00,0x18,0x18,0x18,0x18,0x0C,0x30,0x0C,0x30,0x06,0x60,0x06,0x60,0x03,0xC0,0x03,0xC0,0x01,0x80,0x01,0x80,0x03,0xC0,0x03,0xC0,0x06,0x60,0x06,0x60,0x0C,0x30,0x0C,0x30,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // X +0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x0C,0x30,0x0E,0x60,0x07,0xE0,0x03,0xC0,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Y +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x30,0x00,0x30,0x00,0x60,0x00,0x60,0x00,0xC0,0x00,0xC0,0x01,0x80,0x01,0x80,0x03,0x00,0x03,0x00,0x06,0x00,0x06,0x00,0x0C,0x00,0x0C,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Z +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1B,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // [ +0x00,0x00,0x18,0x00,0x18,0x00,0x0C,0x00,0x0C,0x00,0x06,0x00,0x07,0x00,0x03,0x00,0x01,0x80,0x01,0x80,0x00,0xC0,0x00,0xC0,0x00,0x60,0x00,0x70,0x00,0x30,0x00,0x18,0x00,0x18,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // +0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x00,0xD8,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ] +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0x80,0x03,0xC0,0x03,0xC0,0x07,0xE0,0x07,0xE0,0x0E,0x70,0x0E,0x70,0x1C,0x30,0x1C,0x38,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^ +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00, // _ + +0x00,0x00,0x06,0x00,0x07,0xC0,0x03,0xE0,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ` +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xF0,0x07,0xF0,0x00,0x18,0x00,0x18,0x00,0x18,0x0F,0xF8,0x0F,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x1F,0xF8,0x0F,0xD8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // a +0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1B,0xE0,0x1F,0xF0,0x1E,0x38,0x1C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1C,0x18,0x1E,0x38,0x1F,0xF0,0x1B,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // b +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xFC,0x07,0xFC,0x0E,0x00,0x1C,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x1C,0x00,0x0E,0x00,0x07,0xFC,0x03,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // c +0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x07,0xD8,0x0F,0xF8,0x1C,0x78,0x18,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x1C,0x78,0x0F,0xF8,0x07,0xD8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // d +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x0F,0xF0,0x1C,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0xF8,0x1F,0xF8,0x18,0x00,0x18,0x00,0x1C,0x00,0x0F,0xF8,0x07,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // e +0x00,0x00,0x00,0xFC,0x01,0xFC,0x03,0x80,0x03,0x00,0x03,0x00,0x0F,0xF0,0x0F,0xF0,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // f +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xD8,0x0F,0xF8,0x1C,0x78,0x18,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x1C,0x78,0x0F,0xF8,0x07,0xD8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x38,0x0F,0xF0,0x0F,0xE0, // g +0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x19,0xF0,0x1B,0xF0,0x1F,0x18,0x1C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // h +0x03,0x80,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x80,0x0F,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x0F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // i +0x00,0x38,0x00,0x38,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xF8,0x03,0xF8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x0C,0x18,0x0C,0x18,0x07,0xF0,0x03,0xE0, // j +0x00,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x30,0x18,0x60,0x18,0xC0,0x19,0x80,0x1B,0x80,0x1F,0x00,0x1E,0x00,0x1F,0x00,0x19,0x80,0x18,0xC0,0x18,0x60,0x18,0x30,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // k +0x00,0x00,0x0F,0x80,0x0F,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x0F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // l +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x78,0x3F,0xFC,0x39,0xCC,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // m +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xF0,0x1B,0xF0,0x1F,0x18,0x1C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // n +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xE0,0x0F,0xF0,0x1C,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1C,0x38,0x0F,0xF0,0x07,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // o + +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0xC0,0x1F,0xE0,0x1E,0x70,0x1C,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1C,0x38,0x1E,0x70,0x1F,0xE0,0x1B,0xC0,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00, // p +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xD8,0x0F,0xF8,0x1C,0x78,0x18,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x1C,0x78,0x0F,0xF8,0x07,0xD8,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18, // q +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xF0,0x1B,0xF8,0x1F,0x0C,0x1C,0x0C,0x18,0x0C,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // r +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0xF0,0x1F,0xF8,0x18,0x18,0x18,0x00,0x1E,0x00,0x07,0x80,0x01,0xF0,0x00,0x70,0x00,0x18,0x00,0x18,0x18,0x18,0x1F,0xF0,0x0F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // s +0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x1F,0xF8,0x1F,0xF8,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x0C,0x06,0x1C,0x03,0xF8,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // t +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x18,0xF8,0x0F,0xD8,0x07,0x98,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // u +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x38,0x38,0x18,0x30,0x18,0x30,0x0C,0x60,0x0C,0x60,0x06,0xC0,0x06,0xC0,0x03,0x80,0x03,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // v +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x30,0x0C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x31,0x8C,0x33,0xCC,0x1F,0xF8,0x1E,0x78,0x1C,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // w +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x1C,0x38,0x0C,0x30,0x06,0x60,0x03,0xC0,0x03,0xC0,0x01,0x80,0x03,0xC0,0x06,0xE0,0x0E,0x70,0x0C,0x30,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // x +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x18,0x30,0x18,0x30,0x18,0x30,0x18,0x38,0x38,0x18,0x30,0x18,0x30,0x0C,0x60,0x0C,0x60,0x06,0xC0,0x07,0xC0,0x07,0x80,0x01,0x80,0x03,0x80,0x03,0x00,0x03,0x00,0x3E,0x00,0x3E,0x00, // y +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x38,0x00,0x30,0x00,0x60,0x00,0xC0,0x01,0x80,0x03,0x00,0x06,0x00,0x0C,0x00,0x1C,0x00,0x1F,0xF8,0x1F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // z +0x00,0x00,0x00,0xF8,0x01,0xF8,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x1F,0x00,0x1F,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0xF8,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // { +0x00,0x00,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // | +0x00,0x00,0x1F,0x00,0x1F,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x00,0xF8,0x00,0xF8,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x1F,0x80,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // } +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x8C,0x1F,0xFC,0x18,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ~ +}; diff --git a/components/spiffs_image/image/fonts/swiss721_outline.fon b/components/spiffs_image/image/fonts/swiss721_outline.fon new file mode 100644 index 0000000000000000000000000000000000000000..2ece05ef63436c997aee354d7abc0ae837e566ea GIT binary patch literal 3052 zcmb7GJ#5=X6uuwQ{F6vVqAdnhVGcsUxG*3T0m=jh!XX8OOhl?Z8iK-8s{$D^a`ebS zLx&C=g-jkYdE(?~3OIDgc!LLx8iGJW(exe3J4$wgw)Y_2@!q}f-rv1D*9~5v|Ilbk z8ls3I3=({Xrb3j&g2c>zk24(LfW*gJ5-aJGjglJ3K=;~3jOLxd+L*Z-&ZNI z=_Se$>=^(S>2p^D?uQX9dLf`_&#$@oSFX)2YU>)t^2P!j>#mvAcxfSXjzQeUC!G6$= zQys}24{vCPd?X9<5&PsN^b6SK7QS)Gag9t#C|IE7LjTI905hp8xAEVkx(8@FHA44Et+6)IJFOVI+12y>+fXr(q=cZT)L&z;69RbIAOL?4-$)pgljs zGq5c?431(yy%!GxFY^+>m;SRs+kN78w&&;UZ1nWI>B;nDak4o5csf0;#*cm%`VFIA z*bDE6hvCukXnA<xV3y>dy|MVJRFaE*c!yW%I1#-rWppG)?qZ`|^ZXo{h{POrYuVr1S&M15pLccaX zHS*ED)_&?d;Y`oXFtCA_RZ*Emo^0N+~kO3*D1qPBVfa;z5JLXW#edxvX#+iM#=>GM+u z)X(%pgBtE+dcZx{=)bXi&>PM`O>K6FTDe~Z+UWD!9|KO+@5G75aF3@Fe{SYhY^Dah zQ_S3)*>lvieO!?rEq=Z1$ys>){o@DE1U-5$cA;A(G}cl+q-!2Epr%2V=QN+g9 literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/images/Flintstones.jpg b/components/spiffs_image/image/images/Flintstones.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b0c2514591cbbe06839aaeda69db3050b2e7894e GIT binary patch literal 26319 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 literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/images/animal-silhouettes.jpg b/components/spiffs_image/image/images/animal-silhouettes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..99cece509d2e3414078d1fb0d742175a1cba8085 GIT binary patch literal 11904 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 literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/images/evolution-of-human.jpg b/components/spiffs_image/image/images/evolution-of-human.jpg new file mode 100644 index 0000000000000000000000000000000000000000..154128ea72bca4e0a4fff82298c0c5cb3b0568f3 GIT binary patch literal 12964 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 U0Zq_s^Z#-W?*Bh8LG91{Hw>_Gg8%>k literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/images/girl_silhouettes.jpg b/components/spiffs_image/image/images/girl_silhouettes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..daa05b6bc20bd11a7a7641f2478d81032c6a50d0 GIT binary patch literal 11697 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 T(dX=DwabO0JB8ZY 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 QSvPbV3;!QCaG%ZnA5HED)&Kwi literal 0 HcmV?d00001 diff --git a/components/spiffs_image/image/images/silhouettes-dancing.jpg b/components/spiffs_image/image/images/silhouettes-dancing.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c662bae4478a71df77110adf5d3d24e8e799e27 GIT binary patch literal 15302 zcmbVzc|26_`}Y|`V;}p@C`8!`Sqd|eEeWOUR0zpVD9j-H7D-Wz5?btQMfNTGzOO?_ zVMdH$7QgfPet*yJ_j{hdp65Bn>-^!&abNd+-PiSgzpv{)o;qFvIIij&=>rf51TX}D zfMXhPN%w}{Wed|A7sXwjgNwd^`eNJiI*ryaWOTzeCT!#K6GB!^XnK^Z$GuHv^oEKnO$z z3gHCkI3Z9@$Z

000OBXzD-K{^tdu1IKs*#>m9X0zOc~0nkC9P&#_(Kcfbp4g>!V z&~q|yojiZ}1h@HJ*ePEgrHHh*j1oE(Exb2|@si5-{34l{`S=9{g``eP%bYo@azRy1 zUE`we6+L|eL!+xVEpFYmw6eBwa(21z>gN8yKOpc?P;kiOsHf2}v2pPU>CZD>WWIcr zmHjUNeL-Q-hmW5stEy{i>%M&b*4ozI(b?7gqi1AvYMo(bZ%YxqcLma1CM@u6t3kwkrw$Y4~L>IjW5{DQfT(Ms5g#VgPsm zv;VxodeF6M6dvwY$I62FrWnua-c$j~#b7gJaI@hyZ8RN8!G?{_i|6#}fcpRy`bqp+Fk)loavAm=voMH<$ z!FMxgCrFACF3=c#4B&j>uLf7`GQ-l1YT{T?Qm>v3KYs?e$)IGulrt8_TTPbNh(sye3w!|I!p2gQ(nldUO^q}1iq7-aQibg5#4+H%f`T`j2f z(8EBcm71`t8m!y>Vvl}!YkH-v#G8F`<9wbMCgbt|*PrgjR#*|JRT$%8^y}58p zJS|sHcz8HULeZ?Jc~=Jd;eT^HAP+vENRokf?ZoAtqrPomB5P9Q(0{^w_gO@5uiSFy zOj{kyV(w02J~a3In`Nbn`xvbbV8WAiN-MiMf2*& z?UCT^64zvxgkZaihDl1r#p>dbj1nb)$-lgKTKJx_r#$BR_-0r%;Dcmolx-t8{I&l! z;^}M(w=&|b`Myf1)M_UiUscVGP>Py+zJ(HtK#3@JB9a?fNi0hqsr;lxYkLWu7?(Qb zVx{YYvRr%@498zph!a=n-vA~+Xe`;~7-%v2LldJg;!P96IeJf21+K&^OkN-NF3I5= zxn#|yG1YP^vj2N6+mr)KXfrFu^cbMuJq99UNr}WtLQ2(A89&M6+Gx#p0VyO#2!iTa)j4-`buMq4i`ZT{>j{C%^+~ z4~|{okD(|MSoH8Nlu?7?2i;


G;VFHn7^mM$&;w0m8|4Avr~{W-to7+`ooidgQg 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
9{NOd5b5%s^xk9RGuR4wFAZvWE&zj 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
  • 4(IujtY|v-vf1mr{XEFJHn-0{^_{y=);q!J*9sTfd)w6< zhfNG%H9x!8^-pgWf&=8IxXPa82|{7dR3G{*+4#v0f380Uf9Zd*0w4f4X#dP>-7wzV zARnLY5%%?MxHDD-6MrS?>Src%ebA)j>7AGQw!=|~JE<}>j)l$2;4L6&7?p(OAxm!o zrkU>+#RP7(kM|Xrq;w|OAD#{`H*A%D+XD3QWEvofv9Ye=<5U+yU&lsSB3fThc3#o+ zSj@tSq1dHarR%{%kFs7LE57Gps&LP;qz(vuq-((VvndOD8#C%wz1E@ps&Fo4MvYvu z5CCnZi6*9)2R*W?UVSj-+#4gsX#P&_@!<>d2UyHbK@8wIdDSvhF=vW4TDEd@MPC>d z)RV_oeS=d(B-*nc$V3+TB!a7_IQ-FJx{{@i~Q{`>x=3V;AS0F}@~ zl^aVRzt}hRoBRB&Csfd!LA+17ujzTO_hPg?YjLc1H7*y@DWQ z13w(%=Yl;_(^6hv+g#q9C{tS(V<>6c-Ky50^2}pT4AcHSgV4D=s@P=;mK;B~7=?yX zbjgvc8k^cZ_KTe}G7WYfl#s{j-$QGBC)NH*38-5v6g(WwLB~LMhxKh0|phbOtL=siNDo7^K%pyMuh} z^LfgpjeV?OG=OceKYAfYkh8ZJC1<$javNh5BC(7tk6fO;PwU#@&-2IOFZVB000guf zQ)da*=}9;yJ=vnA30GFa$pnu8v8nI3w0thGabIh5ufLA2OTk^Tixymn0(22%2?Ukf z)s3tG?M4cdd8~+=dC{5!Y-As*(YXmHujzZEp|6eT65C&hblxpIbk6RoQ1^_9f&SOc z(k9T*#-IWkE77=fWgH)c3%g-V42=* z?k?|s8f%hpMG*2$g2o(4ESXPV5unhcHHo#|ShhCM>7J-BKJuVptg0>`ckHZN)sdQY zL6@9+J^@+Gj)L!;j;qv70S8y35J`Dt#o2bzAbcK`Z-A6Ij5hSonz*^cpZAZ%U*TV- z00_VbP~+3ST4I3oX4es$x(Le2ffB5 zSjVQxH#-Vri3i(+a8_OsC&yig>Sy;{S=g2Jj_P|ho5GGr6h}dKVzLKUfLbYHY6*i5lvXnSRsLSNecwVS^ z^HgPbmqCW*+iy8%ZmXOdSS17G%cM}U8-6XNtb`oqM6@AbQkbE@X2% zxxF;=v(i>gcyjDaYPZb#d}v)1WQ4YxYDs`DmrxG0&08^&I~MXf)~2RShn2^ zV#ozV9+F7|8A+TqChBJp3-N*X&=T_ps&AYN9Y13S^FU`isJ>pSl}nX-E@3v9b7TIT zCqVIyLR@nr@81GwtTqcL>M?;_S1cjFn~8=307MGStGf_jjM&5%0*sVfTh9Um%pyS%!u91 za}$K6$r}M4RcT-G0`He&ZR+hFs4DRUoj2fZrNuM}Eo?&PwXSHwV1AcOnoqylwRo&4 zZE1(Uz#osl+P^>n5I_*1@?>OuzAJHez{$XG5ITS(92pQGU!C`&@_e4lOF=t7^3{1# zyRfP6Ca5trXx??i!ZhuiY_AmV;Ay zCgv8q>S$8r*;dS+G35|>mrLUotS-lT3C=SXYysZTOagmCV^jAnUWAPrmWBWnK$BQ(bGuZT^y+QAg`4-&%q`+b5qQmt(~%=Wl8bZ|1JJ+u7DJ zw{)pzwVJKG^H13wg;hL3M_QzcBoft+ku87jZlJ|WnGdukoSJ7+QhI;^Y@y4mR-CP3 z|LKSf1Nh{2&;@k_+x7uoZXZtwBGr(w#5`kd*$84<_tJoC?fQ-O+MB0BFBqTST~1d} zn=85AUIcen{4C%PU~DkYvDI#=*t2d^A{}rZn%EL{(p8R7Fj!xmaji-H5yQEdbM(kK zhslfy3Ar8qLVryDTK}X1Ab>DHU8LX5Nw?`sdoHF-z5vo`C2Rq$xG59ws!wkqbE7=7 z4k*ht{qbP9K&8o5bJPtxwO2jdw@f@3Vu^LH;-C2flsIq|1I-wLUnV%U>T$&}-)^#2Y^UGJ5~&F$to;VT=adX5P;+ z(^%O&0*d4Eh5O*J;;eF@w3q5q3&*fhX~*tZ!^J}eq*)%C};1>?dKd>@KsUG>W%9(umvc6RGcwBJ{!`zzs=eJO46nr`V?A=5Z(ej zS8R`Eiq!4!7yhI2-~Uf300M{r)UWBnu9H3Cf$e7Rs?t_8%S-0|0Vdf(mqO2x076%cQBc;d3hr8*m zAMx7kVHVy8Fj#{tIjkA)0N8_hgphP9pYB3@D|R|>E-zM(K{){<;)7rb>`By!#7r%) z&PN`xa0oMV_n6$_FY?Fbuk%kR00Qg=sFM7I*XI82=ZSr!TMy3n9syu&iHi}BW*lAK zW|}Ch_9%3&n;(1uL>f~3eB3Ja(k6=dGG3Lx!*u|I( z%}pF1-gmz`^7y<@UO(q!v9le!%5EtQKJ8P9=7lU-#pDr8v7i^-2tnTGn!1pxP0HNN z4i*`JO*y}Da6%grZxCv!(xC!2ee7TBYoyKjB!W*@Xjwl^GX6ev-vqjxw;}>wK;mYu zo&-f1_1S4?3-DHsK1ti*zx$8OU+*7Q00a;PsItRU#Uj^NAE(?p+11t~ZoOKOMB$_D zZ6u!z14X+S&zcCx@J4+@yy}^=A@fkgsQUU?c^qY>lT)YMraqo`IhJ7_5nNHlpVUv6Wyq};%PZO`u6}yA{O(;*8x+zzu+6u6 zg?WZ}I?(Qxssrr;xuRW$X%fB@H|?l2{?8)EhTfZ@VWqME}_Q_5VQy{JQo2e;onvBYE?G{k5Fj_WuK70M&Cq z?9)wb2TlZaY~yRN-;1;FPLH5%`QE+#c3P_AjCj4djCWHG(+s_|R>qx$R60ts!S47K z=tDBXk<7dhfk?RMLqzRbGr+}n4q%n9df#++HdE>^%cxOhz8ZAy^tnMO(SxEu>rkcy zfrFaE!DTUUmXOKnREf@9$%d!HRO4`WlBdpLWy#b>7J)Adj!YdM5e$n{(0gjZb%ovP z)dId?9b8RIeM&RGGyz4H&ozCw1|Ke0c${tf@38UO*{ z0JSh`yhS)Mz%$7rA}684QZ`r5NbJZB<}2NH+}5yut7rtlf?c;~mSCowG+Xs8g=!aq z-#Zt1al(vnLc5%?idgl!^u=S@Xn8ZyLCNy06hS4UA}%u`fVqurOKZ`eSgk6Xl4xI@ zP)9j+yTI&Xosh35XZn@TUMa?2a=@ntag0BM`2w4ye6})C@`5?p10U(tdGqTR9naXg zPeNZ0nymYO56v3v9=4D_%~NgTm<9zy9-douutOZc_BI8J#~oi9@qrbR{d!#Yrk@-? zBXU1LxkD%Tf^mhZ;U4Gss@gb!{AxnI@Qpnl91_ReL8}mYe!}A4HV3uu@ZVlf{sXQ5 zV+Z3>xIo_&A84Nv0>`MLASZD^5|9QLOUnTYfD)hrr~w*)7O)@C0rUWUzz|%o|LfZT z2(ag8`{%S3q2LNRV~!j=F?=VBUEcnhgXV|{;$2RM}c$d4@~SdXbfEA18d&E&asHsM zH0C?_qdi(%pcM;2C7-zVQELWDcvp4HcrBq{TRTkg($H4R_xu_78~SgS$4H&&z^|y&o!;1FsLAMw}4mKB-Sk;{tj&dqcnzAoO7fJ zq3H;*{WG$}f)8VoO|=AQ+|YwNbI#8*a_2uS6D@YrW!yA9v|J*547ycOyg!pyzTrACBTn8t800krN9;KXi+|K2|b zf74&700`KsQ2UW&<(UG)#GDAW-I*v)3Lt8CHzK)E$3Au3)J~{M=P)ca#rr)J*kzHJ zY0t`==2@kxPxqp7ZZx0aA$0w&A3oCeTj%28c+)ZI@ylu=^i>&8qU{tq`XHf)Vyt!p zmU|BBwr>GX-MZjBm=`!MeMRlKz^XC`S|!`6b{tu4;GeVDeA|#->*F4Fy;})NoN&b> zl>uz^z}GUe#4MFt8#Ipcog}3We<@>o<@ymrKe&d!+J#Z{?+Um#9mnyBXW?6R;C-&c zIl4KHH6~)lz$Un`_;MB&|5F|hMhY{lN;L*eo%g%+6^GH-0;~@z*F_&&{rJ+ey6YZ0 z{Kfw){LO!r0w90{KnCAO%nZh6Jm2q!9V4 zBRQ;S3pj_$dinBMRfFcMaO*Y|$$;~Ny-ds2l;e<%i{Ned1f!e6+@6|Jd1eM|h{(7m z<2leX#ufOmff$fg7AA49Vd~8rBX@4Yx*kr7I6!6oOhILhks++-H>pFUkpb6T5;NsJs}i~cJfY7oz^=6`c>o(0G>&2NEEteSW15|h z)U~S9$^^q^JB8ii9%e{+KUhDS8ZItN&(o~XtF|tzecpsQAC`*6kGYX0TN=YCyQ%i% zFCqW2{!jAH#NYDoRsaN$0;uUsr?`%-&K&8T! z9*rB*BAj$o5-6-~bLnN;_Q0_fZ9m$gPYrDx?`co`I{B6L`NObeYx32issX8CQaB8aJm)=H!7LdC`4Ug#SlzrmgutiJb|<%O1YW;OcAK+G2~MsVQ5aHGRHy>? zo^kOhH{GfKQhzS~R)41gAmEIGO22PcRHn;x@x3TMZTE-!Ao*ZBZiw@6hR47OM=X$H zH}H``3;c}{C9f^tUb`u8VV>JQ^CpqfTZ+7(mbVe1LAP<=Knw!fp-xpttdXS#R;1X% zXJy)FdF1d5G8E~>B!GGg{7CUFz@yPmISwbRuldH%OY?ofqiK|c#VzOx|Apz!?AnqE z5h-mg=)<6ZiBbSv2!7td&7_{?uiye+Te5&fj6~rFH-Q!@lv}rxfA0BQ6XC297S|&k zsW_ra8pT&Ef#nxJyM-UzZWT}~2{C-W=Mx+Qj@H+b7{WjkVdn?l% zJVpz!4w#oa{H6bF{B8a&1wa59aQ#msU8WOBHKs8E=m$CZBk*zKIKnZj7!|ZNIq{)n z-fnc*IbjE{boM!ReS^m}EHLpoLa8qPOgX1uYOXcxr7~G3jN~N-uBVOpj8+_aeB3Xm zMcEr8S{rP1u08!|0Z1 zvczNMg(z&tO@@*k{xW|){_0E}~gF`JGSBC^sr zzzBqxyS^LoabVE!PY}FJyYq<=#jh51Q$D2%53Wz1TMq*^Vi)9F+N#Bqc`fA|kFFBxeafV_f-^MBrB$pmB_-V-Y;@*{>C4f0WYjF-)WBqRm-y7Q3;EVU zItOZ=0tVkU#q3-Ay2F3ppOL@a|EK^6APZ2FZ$%uGX7xFGL9UA_ZN?(O+9a7$bH0Ds zWitBv0N4y42SpG?1y?%6P(VC}!OE|&t(e{Et@?bRqr6jOn%&m6u0f#obP}2{(V-lF^XMhhSgNvb4M9oZWdj-m z#h4e3mczm0QMx|`i705NpMUULXu$_yxMq&z z)o9&Z+e|>5!N$S~Ry+J<|D60!{tpU(fYwCnTJ*RWlx#W|soSx^vhQZZ4Wj01rR;NU z?UORfdhr)JRQI0r-D5UpV+{wZk_mjWkCY5*!5Y+p)mN)T95>j_rs$;q)MV>ZjJmjL z#rDDUWVXrY)w?-?_sz_?50~z`E7zg=Z28tRrheEH1b#gaAmiq@faR+MeIygg$ePGd zOnw+%m^*Q({+3;NxWX22L9jDo&KwcmqCM45G+MtZG!?b!y?N)WqHPbB0nIv(n)MQ(`k+?38zJO9d+RKKV2pv9);M8#jhw&Hj?gbR#80&_vaSd z^Wwy$iRtZcl%`xRt*@Cf>+JBC`?K6algZ1Z*WJr}&MPB; z!(A4-dT{HUN|1COxE7^cyA8>RhHvKM+}+YHrN>i69)LmX@I)%Zoa|S}g_>H2{21Ln zhxK^>BVC1Q)-TFUGnf0pIvh@s!nU~}=BBX(S7ZuKI0VV&dnqrj$!ETd#|I-loHf0? zQQDmP{&C=qj@@0mPIYN;s;0R##^{~vcD_^t{o+g!!nPq1p0Me)?hR}#MhI>5bPVnG zsC(Tof9B)2OCi+Qy*>|4SR`CIa$NIbw{!@d;M?e%2IvlO)VK#!D2tvm*hJV8X08$; zbKg>wY*5AT)IomQY*U!#f%Q4agySh4u)iTFf(bKVK^-FXXnd6P7O_0wrDjhnN~ zCA(XLc)g1p)Wc)1afw_9zBkg?!9M2l4GbHVd-Ez4PEcM5vLcYawQ*|Qc|gvTKcG}~ z=h*9BC)NG2P8Q+uIWb*qM>!jHcxs{$!7W1QqhN>Bjf|htPNK>_c0gV3!0fF`qgamm z7jRF{7%W_DP+4N4ni@UJhe?6DzW2dthrhy~nZL__sQ?I|2vEOmZ=G=q4zEgQ^}d;% zKsA0$Jvb4(qH8&FPu*q4Qa?}QNQOy%Ri?*!QGO*SpW(L6KOLkL*kngJ2G%hZeF(X) z9l?n`H=F7-(Xqzk8{gS*YD4Rm4`ZzdX0QXP7vwne+POZ-EI;Z|Z-vHDt-7nKQK&+y z=r>|Fr2|2*hD}a%i|w{_Z_ia5Lc_e~{j}?2&*FwXX~VgGr$1%cXsC65KlPMN2aJs@ z1f!r~sA$d zZUcmI5#;+42>m0r?I1S51>DWB8c8|&*B<>}yp96Ey-~Nnwx9p@yjMqnXWQ9y`ystEb0sfSOT7Z-GH2d9WQlaLi=+;gdx)@Qbe^-@1CPoh1bMCC z;L$idegJ~HY&GN}zG`p)_AKnSl%+PYvepQJ`$XhupDrU=uI zAUGl>7ilb96a$}c2qv^2T3-fjSVee~jIwnaIafZapjdUsb$)$C%`nE?^7`hdJaHdh zsm9N_`yi{>*jy1JV{~awh_3tz=lYP~+~DgL3e3ej{K3=@e|-PwQrcIJvtG%2!W4=&Z{WM#Fb2%LYM z%ym<`eE_z`?45Y($^*5E5&#y1z^CTXI596;c-6 z;Qw?BxTiU4Pipaa_jp)dQts&?4~FacvZ}VhsXW$DQ*hK0gLWs@#$xfeZYoDL!Sl(< zHkIIN1hW9DQG|QJ!8g^-vH6rEZ3@MO&oiB`SD$+;;2fTvQD+T(i6bDDsk@;BcarvI z68Zq;AQfrFL)9ZHN3SVoA8pcMSiR3!jtmc1oR>|TI-tajw+K?(;jjGX=YQ^RC;$S+ zQBeI=miJ{mz1ZKI4{ywc5iM)e&=LiHp*FL|UXLBSB6qn?_IX{*=#3t=pf9fI#H+m7 zo%Ez|Z`7V7U??B<0x>;JW8MNN$FObYSbr-xncppSma~P8_EN{?Npi9G?Bpe_t8#wd z+GQw5ZCspRcNLjr^m372mz0>DinU8)JP*5!MZ(CM&Ruyd=m8#(wL{gp)PQ_ zBaaO9lueY~*@^&eMO+Ki_Jh z!o9y^Kbu)TEJhCt-tM0^A2vxXyg(iq0mo2zv;3OPh@+!Qii*{OXp3DvMaH{R5_O{e zTjh(`7#BK14thht4LD@VdfcX2Ner9!5IzV|V+s@6j2Miq5=iwRUYx8=;IRr|I@tBL z{dHYc_5k7|YBpgD7~7md0h==5_$3V6X4ImO0b?$Ns@*uKwwoDECke0)l8!<{DlS3O ziu*|c-@D-fdOuGI{wr7dGy9}hdUown{d^9HbSA3Lfund$l-L%~A*M!ne|dg~zv}-D z{@#D300;Bd21AAh9AG4(N|P0#u|r>b zLgS5<6Ja$Lv|1lCBrFm`w2Uoc#Mpq6PK9BY>-LH`zZ1gHfU8~=~{Yya)H?C@9nzrx@5KPdnL zr~}l0^yd9u=fSrof2(=Jzw>|ae~7>TFBGs{{_Xr%|B~|mb>6FgfBo+niVUK34d%fDI+gzNyq*8)FZmz-vG~vbe*Ak5@C3X8U*MPJr;qv@`LE7dAcgkN}lC znRJ)X~$5&bO6;1Dl`PUB|;^Pa&O3X9;be1-+wZRI~rxT7Qj|qugA6- zkAo|oFrY3qg44It0v!(vF_McwR7K=-$f#FioAv8%}Z5onB`4r+nR@?xT1toPQ5T;+4Xc*4`1ukK<)4c zJHj3N-~T%Q(BDk~^q|3nnsKG%Cm~%&2fW(qFz@T@&o7#NRgT7rwg_W}a4^LdX{!7L z`|2INRJNf$p|l8#*lx14QLoNhvAOHlM=R9I6OFqjQ+4me?u`Xwqr-Y=^h8Wav9~J^ z@kvIpn|GM_tW)Jk^}fPvs~d}4FB3XDw*YzcV>GAw>D_hjf?}o`O!PQIUJ{%TPAh%VMR3P!MRPnXDL92Hb82X)_-vr& zV(jOCrLSuJkmYy<_xE__F%?NfPL-x}1p)A>0CE zB~n`GY5jSXt7(B~_*dM5--mEn@84z_JRW*KZ%lubAK>bGnmwp!o z(1Y_WD&OJ$`n|45)lo6w;^%a8lxPMwNwKD{`LISTJ`X`~#Z6g&YtW>x5_EgS*q@RU z@h<#t@`n}H*4|c)et8yj10T1>E$`fu$t@&)Hn~P4bN>*R?McX2FOnqK;J?Ilxz#Es zR?WugLy7#QbGY40G1zjldY6t*(){AH#D~|~^rS7qqf#Z$-!t56Hksl}*Et#eHFXa{ z9EUGN!1RLV)}|rdEy`m}GLPHt`8uWE7}1<=Ry$GEW+rB3DNc`+8oFQ)Xe%1lBAvV02$+Kp?w|o7@y)$_&WxLJgcKAbo zwEBMuK>rR3px@4ag*#M{a_;nP-^Tu;5{Zv2{rBhB$ou^k1Ul2frLU&Qlvz%FZgS1H zLleRSro;1N8lP*tOV_@Zu!`iT(WzECpX!e>j605h#Ks%t7xW!mRuXfHHt4FknTf#X zBbaC^Xr<=H%3Ni!k2;~1KKJ*14ml{dL=se1IP&F+C2K=*4L1B;T}Sa#_H)^?o3{qf zVnWc4V9n3R;cws=aDIwGBpYH=9w#r^uT$+q!?*Nhsw(dk`<>U41+7wn`qSdZYE25n zsfw(W3W1czKgnlIEq-u~f8yODGvrUTm@7RyVLejK>agmsyd22ypmaLJ=s|e{_mh+d zDyPl%#O&~A`VskKescxkN%-UaYzG#SemnmcJAD=yHUEoD%-{WJEbO3j zBV#iL^lIcxjOX%g7SLo|!SL*pMIYU5hN%K`&HR3wCQGBeXSwg>8wD0*8I)d7Fr+@( z#lNo=VqWP+V+IrVFqmxSVDaFlgRCL5Np6)fpK6%rO2a71j)P4nkpak}Y4yXIT?dkw z--{i-d&5+ES=~tOYibnT!<;W6ySC@D^Lb?DJSy+W`D`kW1&O|%+(9rhjzbEf?st-A z_z0xZu)s>gyDx%}A-cXWo|uZN3r@WfCJbx?9AAUkQov~sCKzl6o59etvT3h{#g$oi zia2HpCkRWgN>3Qq_M5x2qyJy#|N24tKQtJb5&aw20D1t7+@JCX{_Vwc}3k=4(QEcfhT3v9H#SljkU2T>;?8L^=Km)n-0| z)ln11(!a*neFw?r$LeM&^D@`LE@egJ+-XGE?pqGyUl#k=&3u>F2jX%Ml3^4k+8Z~& zVQ^swFFD?b@GwvR>JwirSr+}fk2LgMo|~DcMCN!t<5DkYXF9)rhSmA_ZHQUrmjyho z8=RWMtKiXl$mxWuUDittr-rMPKBQzi_tz=1mDj;|qxf_kcG+Ezv%8k-dD|r?tey;d zW}%6+L5v7ENe?FskWhnqCpF*obPO=Ly82By?C}3({;wa3|K;CQ0rcDXznoP-sPJl@K&<9KoD+JOYDZ{reXvrH5RkjLa(-R3)H6*s<=EUYvP`j(`j_@*?@J5N-v&ha(y}(`9!k;VFWp{!~d80zkV?OvA>}L=(qEK zF|7)weF=oFfo8)F(gqQ^+;^c6OtfwsK<_Fuu7Rt4RZ8SmwSd$us5eywYs5 zUl)nLgQGeMIRYG&>=6j87cA{t9s|``LwK`sX$D4Q=AY3fj1o=toC+$%&aJ;^VDCSQ ze)V9#?ubvo6a3nKS#hNM3YNSFxspyCAk}YPr}`CO263-?PLb`1_Tc_|8|W$fbjCcX z@oYc$Gn$XT@sp+lhL0<`s3Ui)@Fj+t)E#*1`?C8nMSULDi2$h51p5M_#C&>ca2^LL z!lc^oj@zk;5T+GHg}bg578;Kq38&ld2{JQ%3obnBpm79(N_6#`rQ6~E%luzI9RK*= zOab)U`M=PS6kW6wL36Q3(a4psGJXdcP)v4mKr1Vbc<{QKhe$Q|mz%U&G)yv|O_I2& zcDP7Duz3J%izlaPEUW}d>ul9xbc-U|V>1;a=z{X=(AOE(tY6mSo68BFrU)*t-ECm^ z+0($Po4yYwN9fIDE8#sWWzgcHfHiijn4s*f1 zkYwH3g}mkI`Po+^rki(45k6(i#zf`zEr9#Wn@etSgG+FqvA{iA0@iRfLB6T}glhV_ zlMSlIlHP5s&xT}KW5N*3SohgPFcYoY)Z*O9?t{qA)cX!(+xcU$kKL9&`ABZ<@c(80 zs~?ho@^7L5`tAHzz}ecG(LFcKsYTJ@G56F3N4b|+qAX6wrMpw;N3elwlg?e(N>
      TjR``tAHzLm$KVqtm&~^3KJD_82!O?Do>^4Q?MtXcj5#gM26> z%ebvL5+z816Z-p5>TN3DH^RECVkcU7h>}+4pFN%K(}R(s@;O(zJfk2fifXld47!;5;a$d^-n55oGbj7#c6ii)24m z4el946Sh$Fb0wC(n@C-wlPU}0OJZ8USoJd8b5a_LuG4i_>LiZY=iCdpuTJ9-sHtuM z`+hCz`J{E?9>X2}zs!I2!}3r6mn(pNJO9ff%cZ3W?WsHK)3-`>#*XjLVpn?UeJ+jZ$g_6S`V2|c-k2Eo2;WFn&;waTdxI09 zv4A0sRCS7|=>jVS{4hT8(b6(K^{~)2pNC5-4yW9Vi=0g^is!WS2E3K6GP?@d6U1gr zXDJUK31Bq*OX%A!-3MmpGTMt%Dpgw{<57B_@VFH}iXLXLW$&1tJRxGlzgw!g z*1~a4TKt{ftNx;U-W_ZLJN$o{|LOhW--?On z4xY0uyrIl+*xDew0ip|MKvwm3NE&QM9pp705AYw}gjD(tb}HArx#B=oYhHPw@a^Y9 zE^Q4;&A#Yv@ZvDT!zp$%Oj=g2X<@~b1||W{{IsPwhn{!;qckRDAaRD=>Ev%@^f6B}xVJ=j>gzIx?NYeQDtc|s{L%xNf zySr*EcLPg5yI{(A@MZ057P0gISCR%-qW-#Ypf$J)d<+_0cf2VMtnP!z40_tIthlev zG6zwP<;TdiSH+0DvEp>+pI&LXP~msqc_Ghk_v@Hu{PK-bQMrY67joO(JV;{dC;z3q zDH(E0;9JgkWm}z8W~q&1Ew+`r{hQOaL4E*>n9*j0@3IoNY$fv8McD#>o==)wJX5v< zueyI(g`kfEx}Jm$d4q8sJS!#=HQFK_c7*@W`d7c|`JW7={1X`f0i7jyIMk(4TIbSM zTM$LnsHfh{oZd>_lq&M3S+fd7g#5wyA~Pu;y|$-`^P^~2ixNoS8i=7>4(iQlqtJwb zI|GKka=^YSLjS_Ik?}3ku8!O|`HJyK5#CkQWKIrOHGv=f`r8(sIeGiq+k-0j^{trM zN|w~bngUcW*>v;#4?r3HG4A&3vYhL7cih?PV~7cbY(B;3EM;;1Sk{P!i*rbN8(&x_ z58F_cq4T-=q(QU(>R5Kk(sftcn-!XJG5}Vun5yC=gdvoaY*885mIj*)@ZjF<-v_hm zVXZk2I8u`x6d$PbtTxJwYVgZZ?TNHGJ_F?fy|j^W@FRCYQElC1#rKS)7Q`##8uQHmt@W41VWL5L+vn8bK<* zKlxcvd29QQIjvXz)L5k|iZaF}VM&Mg_-^RekWi{~p9w8U2cx)c-YSPC8}fkr19
    • sOU98J*mW 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
    • qWju?A|_ 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 +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "esp_heap_alloc_caps.h" +#include "spiffs_vfs.h" +#include "esp_log.h" + +#ifdef CONFIG_EXAMPLE_USE_WIFI + +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_event_loop.h" +#include "freertos/event_groups.h" +#include "esp_attr.h" +#include +#include +#include "lwip/err.h" +#include "apps/sntp/sntp.h" +#include "nvs_flash.h" + +#endif + + +#include "spi_master_lobo.h" +#include "img1.h" +#include "img2.h" +#include "img3.h" +#include "img_hacking.c" +#include "EPD.h" +//#include "EPDspi.h" + +#define DELAYTIME 1500 + + + +static struct tm* tm_info; +static char tmp_buff[128]; +static time_t time_now, time_last = 0; +static const char *file_fonts[3] = {"/spiffs/fonts/DotMatrix_M.fon", "/spiffs/fonts/Ubuntu.fon", "/spiffs/fonts/Grotesk24x48.fon"}; +static const char tag[] = "[Eink Demo]"; + +//================================================================================== +#ifdef CONFIG_EXAMPLE_USE_WIFI + + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = 0x00000001; + +//------------------------------------------------------------ +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +//------------------------------- +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_WIFI_SSID, + .password = CONFIG_WIFI_PASSWORD, + }, + }; + ESP_LOGI(tag, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +//------------------------------- +static void initialize_sntp(void) +{ + ESP_LOGI(tag, "Initializing SNTP"); + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "pool.ntp.org"); + sntp_init(); +} + +//-------------------------- +static int obtain_time(void) +{ + int res = 1; + initialise_wifi(); + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); + + initialize_sntp(); + + // wait for time to be set + int retry = 0; + const int retry_count = 20; + + time(&time_now); + tm_info = localtime(&time_now); + + while(tm_info->tm_year < (2016 - 1900) && ++retry < retry_count) { + //ESP_LOGI(tag, "Waiting for system time to be set... (%d/%d)", retry, retry_count); + vTaskDelay(500 / portTICK_RATE_MS); + time(&time_now); + tm_info = localtime(&time_now); + } + if (tm_info->tm_year < (2016 - 1900)) { + ESP_LOGI(tag, "System time NOT set."); + res = 0; + } + else { + ESP_LOGI(tag, "System time is set."); + } + + ESP_ERROR_CHECK( esp_wifi_stop() ); + return res; +} + +#endif //CONFIG_EXAMPLE_USE_WIFI +//================================================================================== + + +//============= +void app_main() +{ + + // ======== PREPARE DISPLAY INITIALIZATION ========= + + esp_err_t ret; + + disp_buffer = pvPortMallocCaps(EPD_DISPLAY_WIDTH * (EPD_DISPLAY_HEIGHT/8), MALLOC_CAP_DMA); + assert(disp_buffer); + drawBuff = disp_buffer; + + gs_disp_buffer = pvPortMallocCaps(EPD_DISPLAY_WIDTH * EPD_DISPLAY_HEIGHT, MALLOC_CAP_DMA); + assert(gs_disp_buffer); + gs_drawBuff = gs_disp_buffer; + + // ==== CONFIGURE SPI DEVICES(s) ==================================================================================== + + gpio_set_direction(DC_Pin, GPIO_MODE_OUTPUT); + gpio_set_level(DC_Pin, 1); + gpio_set_direction(RST_Pin, GPIO_MODE_OUTPUT); + gpio_set_level(RST_Pin, 0); + gpio_set_direction(BUSY_Pin, GPIO_MODE_INPUT); + gpio_set_pull_mode(BUSY_Pin, GPIO_PULLUP_ONLY); + +#if POWER_Pin + gpio_set_direction(POWER_Pin, GPIO_MODE_OUTPUT); + gpio_set_level(POWER_Pin, 1); +#endif + + spi_lobo_bus_config_t buscfg={ + .miso_io_num = -1, // set SPI MISO pin + .mosi_io_num = MOSI_Pin, // set SPI MOSI pin + .sclk_io_num = SCK_Pin, // set SPI CLK pin + .quadwp_io_num=-1, + .quadhd_io_num=-1, + .max_transfer_sz = 5*1024, // max transfer size is 4736 bytes + }; + spi_lobo_device_interface_config_t devcfg={ + .clock_speed_hz=40000000, // SPI clock is 40 MHz + .mode=0, // SPI mode 0 + .spics_io_num=-1, // we will use external CS pin + .spics_ext_io_num = CS_Pin, // external CS pin + .flags=SPI_DEVICE_HALFDUPLEX, // ALWAYS SET to HALF DUPLEX MODE for display spi !! + }; + + // ==================================================================================================================== + + + vTaskDelay(500 / portTICK_RATE_MS); + printf("\r\n=================================\r\n"); + printf("ePaper display DEMO, LoBo 06/2017\r\n"); + printf("=================================\r\n\r\n"); + + // ================================================================== + // ==== Initialize the SPI bus and attach the EPD to the SPI bus ==== + + ret=spi_lobo_bus_add_device(SPI_BUS, &buscfg, &devcfg, &disp_spi); + assert(ret==ESP_OK); + printf("SPI: display device added to spi bus\r\n"); + + // ==== Test select/deselect ==== + ret = spi_lobo_device_select(disp_spi, 1); + assert(ret==ESP_OK); + ret = spi_lobo_device_deselect(disp_spi); + assert(ret==ESP_OK); + + printf("SPI: attached display device, speed=%u\r\n", spi_lobo_get_speed(disp_spi)); + printf("SPI: bus uses native pins: %s\r\n", spi_lobo_uses_native_pins(disp_spi) ? "true" : "false"); + + printf("\r\n-------------------\r\n"); + printf("ePaper demo started\r\n"); + printf("-------------------\r\n"); + + + EPD_DisplayClearFull(); + +#ifdef CONFIG_EXAMPLE_USE_WIFI + + ESP_ERROR_CHECK( nvs_flash_init() ); + + EPD_DisplayClearPart(); + EPD_fillScreen(_bg); + EPD_setFont(DEFAULT_FONT, NULL); + sprintf(tmp_buff, "Waiting for NTP time..."); + EPD_print(tmp_buff, CENTER, CENTER); + EPD_drawRect(10,10,274,108, EPD_BLACK); + EPD_drawRect(12,12,270,104, EPD_BLACK); + EPD_UpdateScreen(); + + // ===== Set time zone ====== + setenv("TZ", "CET-1CEST", 0); + tzset(); + // ========================== + + time(&time_now); + tm_info = localtime(&time_now); + + // Is time set? If not, tm_year will be (1970 - 1900). + if (tm_info->tm_year < (2016 - 1900)) { + ESP_LOGI(tag, "Time is not set yet. Connecting to WiFi and getting time over NTP."); + if (obtain_time()) { + } + else { + } + time(&time_now); + } +#endif + + // ==== Initialize the file system ==== + printf("\r\n\n"); + vfs_spiffs_register(); + if (spiffs_is_mounted) { + ESP_LOGI(tag, "File system mounted."); + } + else { + ESP_LOGE(tag, "Error mounting file system."); + } + + //========= + // Run demo + //========= + + /* + EPD_DisplayClearFull(); + EPD_DisplayClearPart(); + EPD_fillScreen(_bg); + //EPD_DisplaySetPart(0x00); + //EPD_DisplaySetPart(0xFF); + + + uint8_t LUTTest1[31] = {0x32, 0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + uint8_t LUTTest2[31] = {0x32, 0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + _gs = 0; + _fg = 1; + _bg = 0; + int n = 0; + while (1) { + //EPD_DisplayClearFull(); + EPD_fillRect(14, 14, 100, 100, ((n&1) ? 0 : 1)); + EPD_fillRect(_width/2+14, 14, 100, 100, ((n&1) ? 1 : 0)); + //LUT_part = LUTTest1; + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + //EPD_wait(2000); + //LUT_part = LUTTest2; + //EPD_DisplayFull(disp_buffer); + printf("Updated\r\n"); + EPD_wait(4000); + n++; + + + n = 0; + printf("\r\n==== FULL UPDATE TEST ====\r\n\n"); + EPD_DisplayClearFull(); + while (n < 2) { + EPD_fillScreen(_bg); + printf("Black\r\n"); + EPD_fillRect(0,0,_width/2,_height-1, EPD_BLACK); + + EPD_fillRect(20,20,_width/2-40,_height-1-40, EPD_WHITE); + EPD_DisplayFull(disp_buffer); + EPD_wait(4000); + + printf("White\r\n"); + EPD_fillRect(0,0,_width/2,_height-1, EPD_WHITE); + EPD_DisplayFull(disp_buffer); + EPD_wait(2000); + n++; + } + + printf("\r\n==== PARTIAL UPDATE TEST ====\r\n\n"); + EPD_DisplayClearFull(); + n = 0; + while (n < 2) { + EPD_fillScreen(_bg); + printf("Black\r\n"); + EPD_fillRect(0,0,_width/2,_height-1, EPD_BLACK); + + EPD_fillRect(20,20,_width/2-40,_height-1-40, EPD_WHITE); + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + EPD_wait(4000); + + printf("White\r\n"); + EPD_fillRect(0,0,_width/2,_height-1, EPD_WHITE); + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + EPD_wait(2000); + n++; + } + + printf("\r\n==== PARTIAL UPDATE TEST - gray scale ====\r\n\n"); + EPD_DisplayClearFull(); + n = 0; + while (n < 3) { + EPD_fillScreen(_bg); + LUT_part = LUT_gs; + for (uint8_t sh=1; sh<16; sh++) { + LUT_gs[21] = sh; + printf("Black (%d)\r\n", LUT_gs[21]); + EPD_fillRect((sh-1)*19,0,19,_height, EPD_BLACK); + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + } + EPD_wait(4000); + + //LUT_part = LUTDefault_part; + printf("White\r\n"); + //EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + //EPD_DisplaySetPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, 0xFF); + LUT_gs[21] = 15; + LUT_gs[1] = 0x28; + EPD_fillRect(190,0,76,_height, EPD_WHITE); + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + EPD_wait(2000); + + EPD_fillRect(0,0,_width,_height, EPD_WHITE); + EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + LUT_gs[1] = 0x18; + EPD_wait(2000); + n++; + } + LUT_part = LUTDefault_part; + } +*/ + + + printf("==== START ====\r\n\n"); + + _gs = 1; + uint32_t tstart; + int pass = 0, ftype = 9; + while (1) { + ftype++; + if (ftype > 10) { + ftype = 1; + for (int t=40; t>0; t--) { + printf("Wait %d seconds ... \r", t); + fflush(stdout); + EPD_wait(100); + } + printf(" \r"); + fflush(stdout); + _gs ^= 1; + } + printf("\r\n-- Test %d\r\n", ftype); + EPD_DisplayClearPart(); + + //EPD_Cls(0); + EPD_fillScreen(_bg); + _fg = 15; + _bg = 0; + + EPD_drawRect(1,1,294,126, EPD_BLACK); + + int y = 4; + tstart = clock(); + if (ftype == 1) { + for (int f=0; f<4; f++) { + if (f == 0) _fg = 15; + else if (f == 1) _fg = 9; + else if (f == 2) _fg = 5; + else if (f == 2) _fg = 3; + EPD_setFont(f, NULL); + if (f == 3) { + EPD_print("Welcome to ", 4, y); + font_rotate = 90; + EPD_print("ESP32", EPD_getStringWidth("Welcome to ")+EPD_getfontheight()+4, y); + font_rotate = 0; + } + else if (f == 1) { + EPD_print("HR chars: \xA6\xA8\xB4\xB5\xB0", 4, y); + } + else { + EPD_print("Welcome to ESP32", 4, y); + } + y += EPD_getfontheight() + 2; + } + font_rotate = 45; + EPD_print("ESP32", LASTX+8, LASTY); + font_rotate = 0; + _fg = 15; + + EPD_setFont(DEFAULT_FONT, NULL); + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print(tmp_buff, 4, 128-EPD_getfontheight()-2); + EPD_UpdateScreen(); + } + else if (ftype == 2) { + orientation = LANDSCAPE_180; + for (int f=4; ftm_sec != _sec) { + _sec = tm_info->tm_sec; + sprintf(tmp_buff, "%02d:%02d:%02d", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec); + _fg = 15; // fill = 15 + set_7seg_font_atrib(10, 2, 0, 15); // outline = 15 + EPD_print(tmp_buff, CENTER, y); + _fg = 15; + if (tm_info->tm_mday != _day) { + sprintf(tmp_buff, "%02d.%02d.%04d", tm_info->tm_mday, tm_info->tm_mon + 1, tm_info->tm_year+1900); + _fg = 7; // fill = 7 + set_7seg_font_atrib(8, 2, 1, 15); // outline = 15 + EPD_print(tmp_buff, CENTER, y2); + _fg = 15; + } + EPD_UpdateScreen(); + } + EPD_wait(100); + } + tstart = clock(); + _fg = 15; + EPD_setFont(DEFAULT_FONT, NULL); + font_rotate = 90; + sprintf(tmp_buff, "%02d:%02d:%02d %02d/%02d", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, tm_info->tm_mday, tm_info->tm_mon + 1); + EPD_print(tmp_buff, 20, 4); + font_rotate = 0; + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print(tmp_buff, 4, 128-EPD_getfontheight()-2); + EPD_UpdateScreen(); + } + else if (ftype == 5) { + uint8_t old_gs = _gs; + _gs = 1; + EPD_drawRect(4,4,20,20, 15); + + EPD_fillRect(27,5,18,18, 1); + EPD_drawRect(26,4,20,20, 15); + + EPD_drawCircle(66,16,10, 15); + + EPD_fillCircle(92,16,10, 2); + EPD_drawCircle(92,16,11, 15); + + EPD_fillRect(185,4,80,80, 3); + EPD_drawRect(185,4,80,80, 15); + + EPD_fillCircle(225,44,35, 0); + EPD_drawCircle(225,44,35, 15); + EPD_fillCircle(225,44,35, 5); + + EPD_fillCircle(225,44,20, 0); + EPD_drawCircle(225,44,20, 15); + + orientation = LANDSCAPE_180; + EPD_drawRect(4,4,20,20, 15); + + EPD_fillRect(27,5,18,18, 1); + EPD_drawRect(26,4,20,20, 15); + + EPD_drawCircle(66,16,10, 15); + + EPD_fillCircle(92,16,10, 2); + EPD_drawCircle(92,16,11, 15); + + EPD_fillRect(185,4,80,80, 3); + EPD_drawRect(185,4,80,80, 15); + + EPD_fillCircle(225,44,35, 0); + EPD_drawCircle(225,44,35, 15); + EPD_fillCircle(225,44,35, 5); + + EPD_fillCircle(225,44,20, 0); + EPD_drawCircle(225,44,20, 15); + orientation = LANDSCAPE_0; + + EPD_setFont(DEFAULT_FONT, NULL); + font_rotate = 90; + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print("Gray scale demo", _width/2+EPD_getfontheight()+2, 4); + EPD_print(tmp_buff, _width/2, 4); + font_rotate = 0; + + EPD_UpdateScreen(); + _gs = old_gs; + } + else if (ftype == 6) { + uint8_t old_gs = _gs; + _gs = 0; + memcpy(disp_buffer, (unsigned char *)gImage_img1, sizeof(gImage_img1)); + + EPD_setFont(DEFAULT_FONT, NULL); + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print(tmp_buff, 4, 128-EPD_getfontheight()-2); + + EPD_UpdateScreen(); + _gs = old_gs; + } + else if (ftype == 7) { + uint8_t old_gs = _gs; + _gs = 0; + memcpy(disp_buffer, (unsigned char *)gImage_img3, sizeof(gImage_img3)); + + EPD_setFont(DEFAULT_FONT, NULL); + _fg = 0; + _bg = 1; + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print(tmp_buff, 4, 128-EPD_getfontheight()-2); + + EPD_UpdateScreen(); + _fg = 15; + _bg = 0; + _gs = old_gs; + } + else if (ftype == 8) { + uint8_t old_gs = _gs; + _gs = 1; + int i, x, y; + uint8_t last_lvl = 0; + for (i=0; i<16; i++) { + for (x = 0; x < EPD_DISPLAY_WIDTH; x++) { + for (y = 0; y < EPD_DISPLAY_HEIGHT; y++) { + uint8_t pix = img_hacking[(x * EPD_DISPLAY_HEIGHT) + (EPD_DISPLAY_HEIGHT-y-1)]; + if ((pix > last_lvl) && (pix <= lvl_buf[i])) { + gs_disp_buffer[(y * EPD_DISPLAY_WIDTH) + x] = i; + gs_used_shades |= (1 << i); + } + } + } + last_lvl = lvl_buf[i]; + } + + EPD_setFont(DEFAULT_FONT, NULL); + sprintf(tmp_buff, "Pass: %d (Gray scale image)", pass+1); + EPD_print(tmp_buff, 4, 128-EPD_getfontheight()-2); + + EPD_UpdateScreen(); + _gs = old_gs; + } + else if (ftype == 9) { + uint8_t old_gs = _gs; + _gs = 0; + memcpy(disp_buffer, gImage_img2, sizeof(gImage_img2)); + + EPD_setFont(DEFAULT_FONT, NULL); + sprintf(tmp_buff, "Pass: %d", pass+1); + EPD_print(tmp_buff, 4, 4); + + EPD_UpdateScreen(); + _gs = old_gs; + } + else if (ftype == 10) { + if (spiffs_is_mounted) { + // ** Show scaled (1/8, 1/4, 1/2 size) JPG images + uint8_t old_gs = _gs; + _gs = 1; + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/evolution-of-human.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/people_silhouettes.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/silhouettes-dancing.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/girl_silhouettes.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/animal-silhouettes.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + EPD_Cls(); + EPD_jpg_image(CENTER, CENTER, 0, SPIFFS_BASE_PATH"/images/Flintstones.jpg", NULL, 0); + EPD_UpdateScreen(); + EPD_wait(5000); + + _gs = old_gs; + } + } + + //EPD_DisplayPart(0, EPD_DISPLAY_WIDTH-1, 0, EPD_DISPLAY_HEIGHT-1, disp_buffer); + tstart = clock() - tstart; + pass++; + printf("-- Type: %d Pass: %d Time: %u ms\r\n", ftype, pass, tstart); + + EPD_PowerOff(); + EPD_wait(8000); + } + +} diff --git a/main/ePaper.h b/main/ePaper.h new file mode 100644 index 0000000..e266961 --- /dev/null +++ b/main/ePaper.h @@ -0,0 +1,73 @@ +/** + ****************************************************************************** + * File Name : main.h + * Description : This file contains the common defines of the application + ****************************************************************************** + * + * COPYRIGHT(c) 2017 STMicroelectronics + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __EPAPER_H +#define __EPAPER_H + /* Includes ------------------------------------------------------------------*/ + +/* USER CODE BEGIN Includes */ +#include "spi_master_lobo.h" + +/* USER CODE END Includes */ + +/* Private define ------------------------------------------------------------*/ + +#define SCK_Pin 18 +#define MOSI_Pin 23 +#define MISO_Pin 19 +#define DC_Pin 17//26 +#define BUSY_Pin 4//32 +#define RST_Pin 16//27 +#define CS_Pin 5 + +spi_lobo_device_handle_t disp_spi; + +/* USER CODE BEGIN Private defines */ +//cs +//dc +//rst +//busy + + +/* USER CODE END Private defines */ + +/** + * @} + */ + +/** + * @} +*/ + +#endif /* __EPAPER_H */ +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/main/img1.h b/main/img1.h new file mode 100644 index 0000000..c597015 --- /dev/null +++ b/main/img1.h @@ -0,0 +1,298 @@ +const unsigned char gImage_img1[4736] = { /* 0X01,0X01,0X28,0X01,0X80,0X00, */ +0XCC,0XCE,0XEC,0XBA,0XCC,0XB3,0XCD,0X10,0X01,0X15,0XEC,0X50,0X01,0X31,0X34,0XA2, +0X33,0X31,0X17,0X45,0X33,0X4C,0X72,0X4C,0X00,0X4A,0X10,0X00,0X00,0X4E,0X41,0X18, +0XCC,0XCE,0XE8,0XBA,0XCC,0XB3,0XAC,0X30,0X01,0X15,0XE0,0X00,0X01,0X10,0XBC,0XC7, +0X33,0X35,0X37,0XED,0X33,0X4C,0XD2,0X84,0X00,0X43,0X10,0X00,0X00,0XEA,0X00,0X18, +0XCD,0XCA,0XCC,0X16,0XCC,0XB3,0X3D,0X52,0X01,0X3C,0XC8,0X00,0X00,0X0D,0XDF,0X20, +0X32,0X35,0X73,0XE9,0X33,0X4D,0XD4,0X28,0X00,0X43,0X20,0X00,0X00,0XA2,0X20,0XDA, +0XCD,0XEE,0X8E,0XB6,0XCC,0XB2,0X6A,0X80,0X01,0X15,0XC8,0X00,0X00,0X58,0X1B,0X01, +0X33,0X11,0X71,0X59,0X33,0X5D,0XB4,0X38,0X00,0X4A,0XA0,0X00,0X00,0X87,0XE4,0XAC, +0XEC,0XEE,0XDE,0XAE,0XCC,0XA3,0X5A,0XC4,0X01,0X35,0X50,0X00,0X00,0X30,0X13,0X42, +0X13,0X33,0X23,0XF1,0X37,0X5C,0XEC,0X28,0X00,0X83,0XA0,0X00,0X00,0XCE,0XCC,0X3C, +0XED,0XCC,0XDC,0X0E,0XC8,0XA3,0X32,0X90,0X00,0X5D,0X40,0X00,0X01,0X21,0X23,0XD3, +0X3A,0X7B,0X6B,0XF1,0X37,0X5D,0XDC,0X68,0X01,0X22,0XA8,0X00,0X00,0X5E,0XCA,0X0C, +0XC5,0X86,0X95,0X5E,0XC9,0XA2,0XB2,0X90,0X00,0X8F,0X44,0X00,0X00,0X20,0X30,0XD3, +0X3B,0X79,0XEE,0XA1,0X36,0X5D,0X5D,0X28,0X22,0X31,0XB8,0X00,0X00,0X1F,0XCF,0X28, +0XCC,0XD6,0X31,0X7E,0XE9,0XA3,0XE0,0XD0,0X00,0X8E,0X44,0X00,0X00,0XA0,0X20,0X97, +0X77,0X2D,0XDF,0X89,0X3E,0X5C,0X3E,0X2C,0X02,0X23,0XB0,0X00,0X00,0X1D,0X2F,0X28, +0X89,0XD2,0XA4,0XF6,0XC3,0XA3,0XD0,0XD0,0X04,0XDE,0XC0,0X00,0X00,0XE2,0XA0,0XC7, +0X7E,0XBF,0X5B,0X29,0X3C,0XDD,0X6E,0X28,0X01,0X03,0X28,0X00,0X00,0X1D,0X0D,0X34, +0XC3,0X40,0XEE,0XDE,0XE3,0X22,0XB0,0XD0,0X04,0X6D,0XD2,0X00,0X00,0XE2,0XF2,0X53, +0X3C,0XFF,0X31,0X63,0X1E,0XDF,0XEF,0X28,0X01,0X16,0XE8,0X00,0X00,0X1D,0X09,0X0C, +0XD7,0X2A,0XCF,0XBC,0XF1,0X20,0X30,0XD0,0X04,0X4B,0X3A,0X00,0X00,0XC2,0XB6,0XA2, +0X69,0XD5,0X74,0XD3,0X8E,0XDF,0XDA,0X28,0X01,0X35,0XCC,0X20,0X00,0X35,0X49,0X59, +0XBE,0XAE,0X9B,0X6E,0XF3,0X22,0XED,0XD0,0X04,0X8E,0XF4,0X00,0X00,0XCA,0XB6,0XA6, +0X45,0X73,0XE5,0XBB,0X4D,0XDD,0X32,0X2C,0X01,0X73,0X3A,0X00,0X00,0X35,0X49,0X49, +0XFB,0X9C,0X3E,0XCD,0XF2,0X26,0XED,0XD0,0X04,0X0D,0XD4,0X00,0X00,0XC2,0XB4,0X35, +0X2C,0XE3,0XD3,0X37,0X2D,0XDB,0XB0,0X20,0X02,0XB7,0X7B,0X00,0X00,0X3D,0X4A,0XC0, +0XD7,0X3E,0XAC,0XDD,0XD6,0X2C,0XDF,0XD0,0X05,0X4D,0XDC,0X80,0X00,0XC4,0XB4,0X3E, +0XB9,0XC9,0X7B,0X62,0X6B,0XD3,0X60,0X20,0X00,0X37,0X77,0X00,0X00,0X33,0X4B,0XC1, +0X4E,0XB7,0X8D,0XBD,0X95,0X2D,0XBD,0XD0,0X04,0X8D,0XBC,0XB0,0X00,0XCC,0XB4,0X34, +0XF5,0X6C,0XF6,0XD2,0X6A,0XD6,0XD2,0X20,0X03,0X76,0XF3,0X40,0X00,0X32,0X4A,0XC3, +0X2B,0X93,0X5B,0X6D,0XB5,0X3B,0X6D,0XD0,0X04,0X0F,0XFC,0X90,0X00,0XCB,0XB5,0X28, +0XDC,0XFE,0XED,0XBA,0XCE,0XCD,0XB2,0X20,0X01,0XB3,0X35,0X40,0X00,0X2C,0X42,0XD7, +0X67,0X4B,0X36,0XE5,0X73,0X36,0XDD,0XD0,0X04,0X4D,0XFE,0X00,0X10,0X93,0X9D,0X28, +0XBA,0XB5,0XDB,0X3E,0X8C,0XDB,0X72,0X20,0X03,0X3E,0XF1,0X40,0X0B,0X6C,0X62,0XD3, +0X4D,0XDB,0X6D,0XD3,0XF7,0X2D,0XCD,0XC0,0X04,0XC7,0XFC,0X00,0X00,0X93,0X94,0X0D, +0XF6,0X6C,0XB6,0XED,0X29,0XFB,0X32,0X30,0X01,0X3B,0XF0,0X10,0X03,0X6C,0X4B,0X60, +0X2B,0XB7,0XDB,0X32,0XDE,0X0C,0XED,0XC0,0X04,0X8F,0XEC,0X00,0X00,0X91,0XB0,0X9E, +0XDD,0X4D,0X6D,0XDF,0XE5,0XF7,0XB2,0X10,0X02,0X75,0XF0,0X00,0X02,0X6E,0X4F,0X61, +0X62,0XF2,0XB6,0XE1,0X3E,0X2A,0XDD,0XC0,0X05,0X0F,0XFC,0X03,0X04,0X91,0X30,0X9C, +0XBD,0X5F,0XDB,0X3E,0XD3,0XFF,0X62,0X2C,0X02,0XFB,0XF2,0X04,0X43,0X6E,0X0F,0X63, +0X57,0XB5,0X6D,0XC3,0XEC,0X33,0X9D,0XC0,0X08,0X0F,0XFC,0X01,0X00,0X91,0XC0,0X9C, +0XEC,0XCA,0XB6,0XFD,0X3B,0XCC,0XE2,0X10,0X03,0XBB,0XF3,0X04,0X02,0X6E,0X3F,0X63, +0X37,0X7F,0XDB,0X23,0XD4,0X7B,0XAD,0XC0,0X08,0X4F,0XEC,0XC2,0X05,0X11,0XC0,0X9C, +0XDB,0XB5,0X6D,0XDD,0XFF,0XAE,0XF6,0X10,0X02,0XBF,0XFB,0X28,0X10,0XCE,0X4B,0X63, +0X6C,0XCA,0XB6,0XE3,0XD2,0XFB,0X51,0XC0,0X0D,0X4F,0XE4,0XD2,0X00,0X31,0X34,0X9C, +0XB7,0X7F,0XDB,0X3D,0XBD,0X2D,0XFE,0X20,0X02,0XB7,0XFB,0X28,0X10,0X4C,0XD3,0X65, +0XDB,0XB5,0X6F,0XF2,0XD6,0XF7,0X51,0X80,0X09,0X5F,0XED,0XD3,0X00,0XB3,0X04,0X12, +0X6C,0XCA,0XB4,0XCF,0X6B,0XBD,0XEE,0X20,0X04,0XAF,0XF2,0X2C,0X00,0X4C,0X2B,0XED, +0XB7,0X7F,0XFB,0X3B,0XBC,0XD6,0XB1,0XD0,0X13,0X5F,0XED,0XC1,0X00,0XB3,0XD5,0X12, +0XDB,0XB5,0X4F,0XE4,0XD3,0X6B,0XEE,0X04,0X04,0XBF,0XF2,0X24,0X00,0X0C,0X21,0X6C, +0X6C,0XDA,0XB4,0X9B,0XAD,0XBD,0X51,0XC2,0X11,0X5F,0XED,0X92,0X00,0XF3,0X4C,0X92, +0XB7,0X6F,0XFF,0XE6,0XFA,0XF7,0XEE,0X30,0X0E,0XBF,0XF2,0X48,0X00,0X0C,0XB3,0X6D, +0XDB,0XBB,0X56,0XBB,0X2F,0X5D,0X31,0X82,0X10,0X5F,0XCD,0X22,0X80,0XF3,0X4C,0X12, +0X6C,0XED,0XAB,0XC6,0XF0,0XAB,0XEE,0X40,0X07,0XBF,0XF2,0X8D,0X00,0X0C,0XB3,0XA9, +0XB7,0XB6,0XFE,0XBB,0XBF,0XFE,0XB5,0X2B,0X10,0X4F,0XC9,0X72,0X21,0X73,0X4C,0X56, +0XDA,0XDB,0XAB,0XEE,0XCA,0XAB,0XDA,0X80,0X0B,0XBF,0XF4,0X88,0X80,0X8C,0XB5,0XA8, +0XEF,0X6E,0XFF,0XFB,0X75,0XFF,0XED,0X28,0X14,0X7F,0XCA,0X27,0X40,0X71,0X42,0X57, +0X3B,0XBB,0XAB,0XFD,0XBE,0XEB,0XB2,0X84,0X03,0X9F,0XF1,0XD0,0X01,0X0E,0XB5,0X08, +0XEC,0XEE,0XFF,0XFF,0XD3,0X7F,0XDD,0X30,0X18,0X6F,0XCC,0X2A,0X80,0XB1,0X40,0X97, +0XB7,0XBB,0XAA,0XFC,0XED,0XEF,0XA2,0XC4,0X07,0XBF,0XF3,0X45,0X00,0XCE,0XBA,0X68, +0XDA,0XEE,0XFF,0XFF,0X3B,0XFF,0XFD,0X10,0X10,0X7F,0XCC,0XB0,0X00,0X31,0X45,0X97, +0X6F,0XBB,0XAD,0XF7,0XD4,0XEF,0XD4,0X42,0X0B,0XBF,0XF3,0X4A,0X00,0XCE,0XB1,0X68, +0XBA,0XEE,0XF3,0X5A,0XFF,0XFF,0XEF,0X0C,0X14,0X7F,0XCC,0XB0,0XA1,0X31,0X4C,0X06, +0XEF,0XBB,0XBE,0XEF,0X53,0XEF,0XB0,0XB0,0X0B,0XBF,0XF3,0X4A,0X00,0XCE,0X83,0XF1, +0XBA,0XEF,0XDB,0XBF,0XAC,0XFF,0XEF,0X42,0X24,0X7F,0XDC,0XB4,0XC0,0X31,0X38,0X1E, +0XEF,0XBA,0XEF,0XD6,0XFB,0XFF,0XB4,0X0C,0X13,0XFF,0XF3,0X6A,0X01,0XCE,0X87,0XC1, +0XBA,0XDF,0XFF,0XEF,0XAF,0XFF,0XDB,0XA0,0X2C,0X3F,0XCC,0X95,0X40,0X31,0X70,0X34, +0XEF,0XFF,0XFF,0X3E,0XF5,0XFF,0X74,0X54,0X03,0XFF,0XFB,0X60,0X01,0XCE,0X8D,0X8B, +0XBB,0X7F,0XFF,0XEF,0X5B,0XFF,0XDB,0X00,0X34,0X7F,0XC4,0X9A,0X80,0X31,0X72,0X74, +0XFC,0XFF,0XFF,0XFD,0XEF,0XFF,0X6C,0XAA,0X0B,0XFF,0XFF,0X45,0X01,0XEE,0X8C,0X8B, +0XFF,0XFF,0XFF,0XF3,0X7B,0XFF,0XB3,0X54,0X24,0X7F,0XD2,0XA8,0X01,0X15,0X73,0X35, +0XFF,0XFF,0XFF,0XFE,0XAF,0XFF,0XEC,0X00,0X1B,0XFF,0XED,0X46,0X00,0XEA,0X8C,0XC8, +0XFF,0XFF,0XFF,0XFF,0XF7,0XFF,0XFB,0X2A,0X44,0X7F,0XDE,0XB9,0X03,0X15,0X72,0X12, +0XFF,0XFF,0XFF,0XD7,0XBB,0XFF,0X64,0XC0,0X33,0XBF,0XF3,0X46,0X08,0XFE,0X8D,0X4C, +0XFF,0XFF,0XFF,0XBF,0XDF,0XFF,0XFB,0X28,0X4C,0XFF,0XCC,0XB9,0XC3,0X01,0X70,0X03, +0XFF,0XFF,0XFF,0XD7,0XFF,0XFD,0X24,0X14,0X13,0X7F,0XF7,0X44,0X28,0XFE,0X8E,0XB4, +0XFF,0XFF,0XFF,0X7F,0XDF,0XFE,0XFA,0XC2,0X6C,0XBF,0XD8,0X32,0X55,0X01,0X70,0X4B, +0XFF,0XFF,0XFF,0XCF,0XBF,0XFF,0XB5,0X28,0X13,0XFF,0XE7,0X4C,0X02,0XFE,0X8B,0X94, +0XFF,0XFF,0XFF,0X3F,0XFF,0XFF,0XDA,0X42,0X4C,0XFF,0XD8,0X12,0X05,0X09,0X74,0X53, +0XFF,0XFF,0XFD,0XF4,0XFF,0XFF,0X65,0X28,0X33,0X7F,0XF7,0X4C,0X00,0XF6,0X83,0X00, +0XFF,0XFF,0XFB,0X5F,0XBF,0XFF,0XBA,0X94,0X4C,0XFF,0XD8,0X12,0X37,0X0B,0X74,0XBB, +0XFF,0XFF,0XFE,0XAA,0XFF,0XFF,0XD4,0X42,0X23,0XFF,0XAF,0X0C,0X80,0XF4,0X8B,0X45, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XEB,0X28,0X5C,0XFF,0XF0,0X22,0X43,0X0B,0XF4,0XB8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XD4,0X93,0X23,0X7F,0XBE,0X9C,0XAC,0XF4,0X0B,0X47, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFA,0X68,0X1D,0XFF,0XC9,0X42,0X03,0X0B,0XF4,0XB8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XCD,0X04,0XC2,0XFF,0X76,0X39,0XAC,0XFC,0X4B,0X47, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF6,0XB3,0X3D,0XFF,0XD9,0X44,0X53,0X07,0XB4,0XA8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XD0,0X48,0X03,0X7F,0X6E,0XB2,0XAC,0XF8,0XDB,0X57, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X24,0XDC,0XFF,0XD1,0X4C,0X53,0X0F,0X64,0XA8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X92,0X23,0XFF,0X2E,0XB3,0X94,0XF2,0XAA,0X57, +0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XFE,0X49,0X5D,0XFF,0XD1,0X44,0X43,0X0D,0X6B,0XA8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X69,0X24,0XA3,0XFF,0X24,0X51,0XA2,0XF2,0XFC,0X57, +0XFF,0XFF,0XFF,0XFF,0X5F,0XFF,0XF6,0X92,0X5D,0XFF,0XDB,0X0C,0X01,0X5F,0X53,0XA8, +0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0XE8,0X49,0X23,0XFF,0X20,0XA2,0X86,0XE9,0XEC,0X57, +0XFF,0XFF,0XFF,0XFF,0X3F,0XFF,0XFF,0X24,0XDC,0XFF,0XCA,0X49,0X41,0X1F,0X53,0XAA, +0XFF,0XFF,0XFE,0XFE,0XFF,0XFF,0XE0,0X92,0X23,0XFF,0X35,0X24,0X0A,0XE4,0XBC,0X55, +0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XFE,0X41,0X5F,0XFF,0XC8,0XDB,0X45,0X5F,0XC3,0XAA, +0XFF,0XFF,0XFF,0X7E,0XFF,0XFF,0XE9,0X3C,0XA0,0XFF,0X32,0X20,0X2A,0XA1,0X7E,0XD5, +0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XF6,0XC3,0X5F,0XFF,0XC9,0X49,0X15,0X5E,0XB3,0X58, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE9,0X14,0X23,0XFF,0X34,0X34,0XCA,0XA5,0XDD,0X47, +0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XFE,0X49,0XDD,0XFF,0XCB,0X82,0X05,0X5A,0X67,0XF8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE1,0XA6,0X23,0XFF,0X74,0XB8,0XAA,0XA5,0XB9,0X57, +0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XFE,0X10,0XDF,0XFF,0XC3,0XC2,0X2F,0XFB,0X47,0XAC, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE9,0XCB,0X23,0XFF,0XFD,0X7F,0XFF,0XFC,0XB8,0XF7, +0XFF,0XFF,0XFF,0XFF,0X7F,0XFF,0XF6,0X34,0XBF,0XFF,0XC3,0XFF,0XFF,0XFF,0XE7,0X08, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE9,0X82,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X98,0XF7, +0XFF,0XFF,0XFF,0X7F,0X7F,0XFF,0XFE,0X5B,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X67,0X48, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE1,0XA7,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XD0,0XBF, +0XFF,0XFF,0XFF,0XFF,0X7F,0XFF,0XFE,0X5B,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X3E,0XF4, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF1,0X45,0X54,0XF7,0XFF,0XFF,0XFF,0XFF,0XCB,0X8B, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XCC,0X3A,0XAB,0XF7,0XFF,0XFF,0XFF,0XF4,0XFE,0XFE, +0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0XF0,0X85,0X3A,0XFF,0XFF,0XFF,0XFF,0XDB,0X33,0X53, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X0A,0XA2,0XFF,0XC7,0XFF,0XFF,0XF5,0X6F,0XCF,0XED, +0XFF,0XFF,0XFD,0X7F,0XFF,0XFF,0X37,0XE9,0X5F,0XFF,0XFF,0XFB,0X5F,0XBD,0X7B,0X3F, +0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0X0F,0XF0,0X3F,0X8F,0XFF,0XFD,0XF4,0XFF,0XAE,0XD2, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X0F,0X48,0XD7,0XF7,0XFF,0XEE,0X0B,0X3D,0XF7,0XBF, +0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0X15,0XB4,0X0F,0X3F,0XFC,0XF3,0XFF,0XD2,0XDA,0XE9, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X8C,0XD0,0X16,0XF7,0XF3,0XFD,0X57,0XFF,0X6F,0XBF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X82,0XC0,0X0F,0XFA,0XDF,0X4A,0XBB,0XCD,0XBA,0XD4, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X1D,0XFF,0X34,0XFF,0XDF,0X76,0X4F,0XEB, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBC,0X00,0X3F,0XFE,0XEB,0XB7,0X57,0XBB,0XF5,0X3E, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XD2,0X02,0XBB,0XFF,0X1E,0XD9,0XCC,0XCD,0X2E,0XCB, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XAD,0XCC,0X7E,0X7C,0XF3,0X76,0X33,0X76,0XD3,0XFC, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFA,0X23,0XD7,0X9A,0X1C,0XC9,0X8A,0XAB,0XAC,0X9F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XAF,0XDC,0XE4,0X34,0X6B,0X36,0X45,0XD6,0XF7,0XF5, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X23,0X12,0XCB,0X3E,0XC9,0XBA,0X3B,0X1B,0XAA, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XB3,0XD4,0XD1,0X38,0XCD,0X36,0X47,0XCE,0XF4,0XD5, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XDE,0X2B,0X1E,0XCF,0X73,0XC9,0XBD,0X7B,0X0F,0X6A, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFB,0XD4,0X00,0X31,0X8C,0X36,0X52,0X8D,0XF2,0X97, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X2B,0XA2,0XFE,0XF3,0XC9,0X4F,0XFB,0X3F,0XF9, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X7B,0XC4,0X0F,0XFB,0X4C,0X34,0X3D,0X2D,0XDB,0X4E, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X3B,0XF3,0XFC,0XB3,0XC0,0X42,0XD7,0X7D,0XB7, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X7B,0XC4,0X0F,0XFF,0X5C,0X38,0X3F,0XA8,0X8A,0XCB, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBC,0X33,0XFF,0XFC,0X83,0XC4,0X44,0XF7,0XF5,0X74, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0XDB,0XCC,0X1F,0XFB,0X7E,0X20,0X3F,0X5A,0XAA,0XAF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X7E,0X33,0XEF,0XFC,0X81,0X90,0X81,0XAF,0X7F,0XF2, +0XFF,0XFF,0XF7,0XFF,0XFF,0XFE,0XDB,0XCC,0X3F,0XF7,0X7E,0X40,0X1E,0XF9,0XD4,0X4D, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X3D,0X33,0XDF,0XF8,0X01,0X80,0X03,0X4E,0X2B,0X3E, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFA,0XCC,0X3E,0XE7,0XFE,0X00,0X2D,0XB7,0XFE,0XE3, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X5F,0X22,0XEF,0X38,0X01,0XCB,0XEA,0XF9,0X75,0X3C, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBC,0XBB,0X3F,0XEA,0XBE,0X3F,0XFF,0XFF,0XBF,0XCB, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF3,0XBC,0XF3,0XFF,0XCA,0XFF,0XFF,0XFC,0XD4,0XFE, +0XFF,0XFF,0XFB,0XFF,0XFF,0XFF,0XDF,0XFF,0XFF,0XFF,0XFF,0XFF,0XDF,0XFF,0X7F,0X35, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0X3F,0XFF,0XFF,0XFF,0XFF,0XF0,0X95,0XCA, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XCF,0XFF,0XFF,0XFF,0XDF,0XFF,0XEE,0XF7, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBF,0XF5,0X70,0X0E,0XFF,0XFF,0XFF,0XFE,0XF5,0X2D, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X58,0XEF,0XFF,0XFF,0XFF,0XDD,0X7F,0X1B,0XF6, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBE,0XA7,0XF0,0X2C,0XFF,0XFF,0XEF,0XD5,0XEC,0XA9, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XDF,0XCB,0XEC,0XFF,0X7F,0XFF,0XB5,0XBF,0X53,0XFE, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XBF,0XF5,0X73,0XFC,0XFF,0XFF,0XEA,0XD4,0XEC,0XD3, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC4,0X0A,0XCC,0XFF,0XFF,0XFF,0X7D,0X6F,0XBB,0X2C, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE8,0XD5,0X33,0XFC,0XFF,0XFF,0XAB,0XBE,0XFD,0XD7, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF7,0X20,0XCC,0XFF,0XFF,0XFF,0XEC,0XFB,0XBE,0XAA, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0XCB,0X33,0XFC,0XFE,0XFF,0XF7,0XFC,0XD3,0X6B, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X34,0XCD,0XFF,0XFF,0XFF,0XCB,0XBF,0XFD,0XAF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF4,0XCF,0X33,0XFD,0X3F,0XFF,0XFF,0XD2,0X0E,0X78, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X32,0XCD,0XFF,0XF4,0XFF,0XCC,0XFD,0XF3,0XDF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF4,0XCD,0X33,0XFD,0X1F,0XFF,0XFF,0XDF,0XFD,0X32, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X32,0XC4,0XFE,0XF3,0X7F,0XDF,0XF5,0X57,0XDF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFB,0XF4,0XCD,0X3B,0XF9,0X0D,0XFF,0XFD,0X7B,0XDD,0X6A, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XFF,0X36,0XC0,0X7E,0X32,0X7F,0XBF,0XDF,0X62,0X95, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFB,0XF4,0XCB,0X28,0X3C,0X8D,0X7F,0XFC,0XF4,0XDF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X34,0XD6,0X92,0X20,0X5F,0XFF,0X4F,0X33,0X01, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFB,0XF2,0XCB,0X29,0X25,0XD2,0X17,0XFC,0XBD,0XCF,0XFE, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XFE,0XB5,0XD6,0XD8,0X0C,0X0F,0XFF,0XE6,0XFC,0XA1, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF7,0XFB,0X4A,0X69,0X21,0XE0,0X05,0XFF,0XFB,0XAB,0XDB, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XFC,0XB5,0XBA,0XDA,0XEB,0XB3,0X3F,0XFE,0X77,0X6D, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF3,0XFF,0X4E,0XCD,0X65,0XF4,0X48,0XDF,0XFB,0XDC,0XBD, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF2,0XB3,0X72,0X9B,0XFB,0X36,0X37,0XCF,0X23,0XE7, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF1,0X3D,0X4D,0XBF,0X6C,0XCC,0X81,0XC9,0X7D,0XFF,0X5C, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF6,0XDA,0XB3,0X53,0X93,0X73,0X6A,0X16,0XB7,0X54,0XA3, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFB,0XFF,0X4C,0XBC,0X6C,0X8C,0X94,0X0B,0XCA,0XAF,0XDC, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF5,0X38,0XB3,0X4F,0X93,0X73,0X40,0X34,0XFF,0XF5,0X73, +0XFF,0XFB,0XFF,0XFF,0XFF,0XFB,0XCF,0X4D,0XB5,0X6C,0X8C,0XBA,0XAF,0X3C,0X2A,0XCE, +0XFF,0XFE,0XFF,0XFF,0XFF,0XE5,0X7A,0XB6,0XEA,0X93,0X73,0X45,0X11,0XC3,0XFB,0X39, +0XFF,0XFF,0X7F,0XFF,0XFF,0XFB,0XAD,0X4B,0X3D,0X4C,0X8C,0XB0,0XEE,0XFF,0X5D,0XD7, +0XFF,0XF3,0XFF,0XFF,0XFF,0XED,0XFA,0XBC,0XC2,0XA3,0X73,0X4E,0X13,0X5D,0XEA,0X2C, +0XFF,0XFF,0X3F,0XFF,0XFF,0XF3,0XFF,0XD7,0X7E,0X5C,0X84,0XA1,0XEE,0XEE,0XDF,0XD7, +0XFF,0XFD,0XFF,0XFF,0XFF,0XCD,0XFA,0XE9,0XA3,0X83,0X7B,0X56,0X13,0X13,0XEB,0X68, +0XFF,0XFF,0X3F,0XFF,0XFF,0XF7,0XFF,0X36,0X5F,0X7C,0X84,0XA9,0XEE,0XFF,0XBB,0XBE, +0XFF,0XFF,0XDF,0XFF,0XFF,0XEB,0XF9,0XD9,0XAC,0X82,0X51,0X56,0X13,0X8C,0XCC,0X4B, +0XFF,0XFC,0X3D,0XF7,0XFF,0XFF,0XFE,0XE6,0XF7,0X5C,0X0E,0XAB,0XEE,0XF7,0XF7,0XA9, +0XFF,0XFF,0XDF,0X0D,0XDF,0XE5,0XFB,0X1B,0XAA,0XA2,0X01,0X55,0X13,0XAB,0X2A,0XEA, +0XFF,0XFC,0X4C,0XF2,0XFF,0XFB,0XFD,0XEC,0XFD,0X55,0X06,0XAA,0XEE,0XDC,0XFF,0X3D, +0XFF,0XFE,0XF7,0XFD,0X2F,0XEF,0XFA,0X57,0X33,0XA8,0X01,0X55,0X17,0X37,0XCD,0XD7, +0XFF,0XFB,0X09,0X52,0XDF,0XF5,0XFD,0XAA,0XFC,0X56,0X0E,0XAE,0XAB,0XCD,0X72,0X7C, +0XFF,0XFC,0XF4,0X8D,0X7F,0XFB,0XFA,0X7D,0X93,0XA8,0X21,0X51,0X17,0X7A,0XAF,0X83, +0XFF,0XFF,0X0B,0X6E,0XFF,0XF7,0XFD,0X86,0X6C,0X54,0X8E,0X2C,0XEC,0XCF,0XFD,0X7C, +0XFF,0XFC,0XD0,0X39,0XFF,0XFB,0XFA,0XF9,0X93,0X00,0X21,0X50,0X17,0XB5,0XF2,0X83, +0XFF,0XFA,0X01,0XF7,0XFF,0XEF,0XFD,0X06,0X6C,0X80,0XDE,0X84,0XAD,0X5A,0X2F,0X7C, +0XFF,0XFE,0XC0,0X9B,0XEF,0XF3,0XFA,0XFB,0X92,0X2A,0X00,0X52,0X52,0XD7,0XDD,0XD3, +0XFF,0XF9,0X03,0X67,0X7F,0XDF,0XFF,0XA4,0X6C,0X80,0XAB,0X05,0X0F,0X6B,0X6B,0X2C, +0XFF,0XFE,0X00,0XDF,0XDF,0XF3,0XFA,0XDF,0XD3,0X2A,0X54,0X00,0XF1,0XB4,0XBC,0XF3, +0XFF,0XF4,0X03,0X38,0X7F,0XCF,0XFF,0X62,0X2C,0X14,0X00,0X01,0X0E,0XCB,0XD7,0XAC, +0XFF,0XF0,0X04,0X47,0XCF,0X7D,0XFA,0XBF,0XF3,0XC0,0X40,0X00,0X53,0X34,0XAA,0XFF, +0X5D,0XFC,0X00,0XB9,0X35,0X83,0XFF,0XFF,0XFA,0X2B,0X0B,0X00,0X0F,0XEB,0XFF,0XFF, +0X86,0XF4,0X84,0X44,0XC8,0XFF,0XFF,0XFF,0XF9,0X50,0XE4,0X20,0XA0,0XBF,0XFF,0XFF, +0XB9,0X72,0X00,0X42,0X34,0X2F,0XFF,0XFF,0XF4,0XAF,0X1B,0X90,0X1B,0XFF,0XFF,0XFF, +0XC2,0X08,0X00,0X05,0X13,0XFF,0XFF,0XFF,0XFA,0X50,0XE4,0X48,0XAF,0XFF,0XFF,0XFF, +0XF9,0XB2,0X00,0X08,0X0D,0XFF,0XFF,0XFF,0XFD,0XAF,0X13,0X23,0XFF,0XFF,0XFF,0XFF, +0XE9,0X44,0X00,0X03,0XB3,0XFF,0XFF,0XFD,0XFA,0X50,0XCC,0XBF,0XFF,0XFF,0XFF,0XFE, +0XF8,0X00,0X00,0X0C,0X07,0XFF,0XF5,0X07,0XFD,0XAF,0X23,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFE,0X00,0X00,0X10,0X1F,0XFF,0XFF,0XF9,0XFE,0X50,0X9F,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF4,0X00,0X00,0X04,0X3F,0XFF,0XF4,0X07,0XFF,0XAE,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE, +0XF8,0X00,0X00,0X00,0XFF,0XFF,0XFB,0XF8,0XFF,0X53,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XE4,0X00,0X00,0X00,0XFF,0XFF,0XFC,0X3F,0X7C,0XAF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF8,0X00,0X08,0X03,0XFF,0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XE0,0X00,0XBC,0X0F,0XFF,0XFF,0XFD,0XFF,0X7E,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF0,0X0B,0XFE,0X0F,0XFF,0XFF,0XEA,0XFF,0XBF,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XC0,0X0F,0XFF,0XFF,0XFF,0XFC,0XBF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF0,0X6F,0X7F,0XFF,0XFF,0X3F,0XEF,0XFF,0XFF,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XC0,0X3F,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF0,0XFC,0X3D,0XFE,0XFF,0XFF,0XFF,0XFF,0XFF,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XC0,0XEE,0XF2,0XFF,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XA0,0XF4,0X7F,0XF1,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD, +0XC0,0XDF,0XF5,0XFE,0XBF,0XFF,0XFF,0XFF,0XFF,0XBF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XA0,0X75,0XFF,0X31,0X3F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD, +0XC0,0X54,0X55,0XEA,0XDF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0X80,0X54,0X21,0X14,0X3F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF5, +0XC0,0X00,0X48,0XC2,0X9F,0XFF,0XFF,0XFF,0XFF,0XDF,0XFF,0XFF,0XFF,0XFF,0XFE,0X35, +0X80,0X00,0XB2,0X3E,0X87,0XFF,0XFF,0XDF,0XFF,0XBF,0XFF,0XFF,0XFF,0XFF,0XFF,0XDA, +0XE0,0X00,0X4C,0XFF,0X67,0XFF,0X34,0XAF,0XFF,0XDF,0XFF,0XFF,0XFF,0XFF,0XFE,0XFB, +0X00,0X00,0X23,0XFF,0XBB,0XFF,0XCF,0XFF,0XFF,0X5F,0XFF,0XFF,0XFF,0XFF,0XFF,0X3E, +0XC0,0X00,0X5F,0XFF,0X7F,0XFF,0XB0,0XD7,0XFF,0X2F,0XFF,0XBF,0XFF,0XFF,0XFF,0XF7, +0X00,0X00,0X7F,0XFF,0XDF,0XFD,0XCB,0X7F,0XFC,0XB7,0XFF,0XDF,0XFF,0XFF,0XE4,0XB1, +0XC2,0X00,0X3F,0XFF,0X3F,0XFB,0X20,0XF5,0X5E,0X0B,0XFD,0X7F,0XFF,0XFF,0XFF,0XDC, +0X00,0X00,0X7F,0XFF,0XEF,0XFD,0XDC,0XEE,0XB1,0X7D,0X57,0XDF,0XFF,0XF5,0XF7,0X57, +0X84,0X00,0X3F,0XFE,0X17,0XFF,0X20,0X7F,0XF4,0X07,0XFD,0X77,0XFF,0XF3,0XDC,0XA8, +0X00,0X80,0XBF,0XFF,0XFF,0XFE,0XD8,0XBF,0XE2,0X79,0X02,0X88,0X7F,0XDC,0X67,0XF7, +0X20,0X00,0XFF,0XFE,0X0F,0XFD,0X23,0X47,0XC2,0X8F,0XFD,0X55,0X8C,0XF3,0XDA,0X1E, +0X00,0X00,0X7F,0XFF,0XFF,0XEB,0XD8,0X39,0X42,0X74,0X4A,0X80,0X73,0X4C,0XAF,0XF3, +0XC6,0X02,0XFF,0XFE,0XBF,0XEC,0X20,0X2F,0XE3,0X8B,0X25,0X6A,0X9E,0XAB,0X78,0X5E, +0X0A,0X47,0X3F,0XFD,0X7F,0X37,0XB8,0X31,0X22,0X74,0XBA,0X85,0X6B,0X77,0X47,0XA3, +0X86,0X54,0XFF,0XFF,0XBF,0XDC,0XA6,0X76,0XC5,0X8B,0XE4,0X60,0X55,0X9C,0XFC,0XFC, +0X2F,0X0F,0X5F,0XFC,0XFE,0XE2,0XD8,0X7B,0XB4,0X7E,0XFB,0X13,0X3E,0XE3,0X27,0X0B, +0X8C,0X83,0XBF,0XFF,0X3F,0X3F,0X44,0X24,0X42,0XB3,0X04,0XC0,0XC3,0X5E,0XDB,0XF4, +0X2E,0XDF,0XFF,0XFC,0XFE,0XF9,0X34,0XFB,0X02,0XCD,0XF2,0X3D,0X3D,0XA5,0X34,0XCF, +0XCC,0X3B,0XFF,0XFF,0XFF,0XBE,0XC2,0X14,0XA3,0X32,0X0C,0X80,0XD6,0XFD,0XCF,0X31, +0X1E,0X5F,0XFF,0XF8,0XFE,0XF5,0X68,0XA3,0X5A,0XDD,0XE3,0X75,0X69,0X52,0X74,0XDE, +0XED,0XBD,0XFF,0XFF,0X7F,0X5B,0X12,0XC8,0X2D,0X62,0X18,0X0A,0XB7,0XAD,0XAB,0X61, +0X1A,0X7F,0XFF,0XF9,0XFD,0XAD,0XE3,0X30,0X82,0XDD,0X45,0XF1,0X58,0XF3,0X5E,0X9E, +0XEF,0XFB,0XFF,0XFE,0XFB,0XF3,0X18,0X8D,0X3D,0X6A,0X3A,0X0E,0XCF,0XDD,0XE3,0XE1, +0X17,0X7F,0XFF,0XFB,0XFC,0XFC,0XE2,0XA2,0X07,0XB5,0X85,0XE1,0X74,0XAD,0X1D,0X5B, +0XBF,0X7B,0XFF,0X7C,0XFB,0XFB,0X1D,0X19,0XF8,0XCE,0X5A,0X1C,0XAB,0XF7,0XF3,0XAC, +0XDF,0XBF,0XFF,0XBB,0XFE,0XFC,0XE2,0XC2,0X07,0X31,0XA5,0XE3,0XFE,0X8C,0X1C,0XB3, +0XBF,0XEB,0XFF,0XFF,0XDF,0XFB,0X39,0X3D,0XC8,0XD6,0X5A,0X1C,0X15,0XF3,0XEF,0XCE, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0XC6,0XC2,0X0F,0X49,0XA5,0XE3,0XEB,0X5C,0XDA,0X3B, +0XBF,0XFB,0XFF,0XFF,0XFF,0XFF,0X79,0X3D,0XAA,0XB6,0X5A,0X1C,0X3E,0XE3,0X25,0XDC, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X86,0XCA,0X5D,0X49,0XA5,0XE3,0XD3,0X3E,0XDE,0X27, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X79,0X35,0X0F,0XB6,0X52,0X1C,0X6D,0XFF,0X77,0XD8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X8E,0XCA,0XF4,0X69,0XA9,0XE3,0X12,0X11,0XCD,0X67, +0XFF,0XFF,0XFE,0XFF,0XFF,0XFF,0XF1,0X35,0X0B,0X96,0X56,0X1C,0XEF,0XEE,0X76,0XB8, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X8E,0XCA,0XFC,0X6B,0XA9,0XE3,0X11,0X51,0X89,0X4F, +0XFF,0XFF,0XFE,0XBF,0XFF,0XFF,0XF1,0X35,0X37,0XB4,0X56,0X1C,0XEE,0XAE,0X7B,0XFA, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XCC,0XAA,0XD8,0X4B,0X91,0XE2,0X1B,0XF1,0XAD,0X2D, +0XFF,0XFF,0XFE,0XDF,0XFF,0XFF,0XF3,0X55,0X6F,0XB0,0X4A,0X1D,0XA5,0X1E,0X72,0XF2, +0XFF,0XF7,0XFF,0X2F,0XFF,0XFF,0XCC,0XBA,0X90,0X4D,0X15,0XE2,0XDA,0XE5,0X8F,0X8D, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF2,0X45,0XEF,0XB2,0X82,0X14,0X6F,0XEA,0XF0,0X72, +0XFF,0XF7,0XFD,0X57,0X7F,0XFF,0XCD,0XBA,0X30,0X48,0X09,0XEB,0X11,0X35,0X0F,0XAD, +0XFF,0XFF,0XFF,0XEF,0XFF,0XFF,0XF2,0X65,0XCD,0XB2,0XB4,0X10,0X4E,0XCA,0XFB,0XD2, +0XFF,0XF7,0XFD,0X3F,0XFF,0XFF,0XDD,0X9A,0X30,0X4D,0X4B,0X6C,0X0B,0XFF,0X0C,0XAD, +0XFF,0XFF,0XF3,0XDF,0XFF,0XFF,0XE2,0X65,0XC3,0X32,0XB0,0X80,0X01,0X10,0XF7,0X52, +0XFF,0XF7,0XFD,0X7F,0XFF,0XFF,0XFD,0XBA,0X78,0XC9,0X4C,0X72,0XA0,0XAB,0X0B,0XAD, +0XFF,0XFB,0XF6,0XDF,0XFF,0XFF,0XF2,0X45,0X84,0X36,0XB2,0X8D,0X0E,0XDC,0XF4,0X57, +0XFF,0XEF,0XFB,0X3F,0XFF,0XFF,0XFD,0XBB,0X5B,0X41,0X45,0X60,0XB3,0XA7,0X3F,0XA8, +0XFF,0XFF,0XFD,0XFF,0XFF,0XFF,0XF2,0X4D,0X80,0X0A,0XB2,0X96,0X1C,0X58,0XC4,0XF7, +0XFF,0XEF,0XFA,0X5F,0XFF,0XFF,0XFE,0XB2,0X6D,0X01,0X49,0X48,0XA3,0XEB,0X3B,0X50, +0XFF,0XFF,0XFF,0XBF,0XFF,0XFF,0XF3,0X6D,0X90,0XCA,0X84,0X37,0X0D,0X3D,0XEB,0XAF, +0XFF,0XEF,0XFA,0XFF,0XFF,0XFF,0XFC,0XD2,0X44,0X25,0X22,0X88,0XA2,0XC3,0X3C,0XD0, +0XFF,0XFF,0XFD,0X3D,0XFF,0XFF,0XEF,0X2D,0X03,0X12,0XC9,0X77,0X5D,0X3D,0XD3,0X2F, +0XFF,0XEF,0XFB,0XFF,0XFF,0XFF,0XF2,0XF6,0XA8,0XE9,0X04,0X08,0XA6,0XC0,0XBD,0XF0, +0XFF,0XFF,0XFC,0X7C,0XFF,0XFF,0XEE,0X89,0X53,0X14,0X32,0XB6,0X59,0X3F,0X46,0XDE, +0XFF,0XEF,0XF7,0XB7,0XFF,0XFF,0XFB,0X76,0XAC,0X41,0X0C,0X49,0XA6,0XC2,0XBB,0X31, +0XFF,0XF7,0XFC,0XFB,0XFF,0XFF,0XED,0X89,0X51,0X20,0XD3,0XB4,0X5B,0X3F,0XD4,0XDE, +0XFF,0XDF,0XF3,0X3E,0XFF,0XFF,0XFA,0XF6,0XAE,0X80,0X2C,0X02,0XAC,0XD5,0X4F,0X31, +0XFF,0XF7,0XDC,0XFB,0XFF,0XFF,0XEF,0XF9,0X41,0X20,0XC3,0XF8,0XD3,0X0B,0X70,0XCE, +0XFF,0XCF,0XAB,0XFE,0XDF,0XFF,0XF5,0X76,0XB4,0XD0,0X3C,0X02,0X2E,0XF4,0X8F,0X31, +0XFF,0XFF,0XFC,0X7B,0X7F,0XFF,0XFE,0XFB,0X41,0X00,0X81,0XFC,0XD3,0XBB,0XF0,0XCE, +0XFF,0XEF,0XEB,0XFD,0XDF,0XFF,0XEB,0XEC,0X80,0X03,0X7C,0X03,0X2C,0X4E,0X2F,0X71, +0XFF,0XFF,0XF4,0XFB,0X3F,0XFF,0XFE,0XF7,0X41,0X0C,0X02,0XBC,0X93,0XF1,0XD0,0X8D, +0XFF,0XFF,0XFB,0XFD,0XCF,0XFF,0XEB,0XFA,0X80,0XA0,0X28,0X43,0X6C,0X1E,0X2F,0X70, +0XFF,0XFF,0XCD,0X7A,0X3F,0XFF,0XFE,0XF5,0X4A,0X48,0XD7,0X3C,0X93,0XE9,0XD0,0X8A, +0XFF,0XFF,0X72,0XFD,0XCF,0XFF,0XEB,0XFE,0XB4,0XA2,0X28,0XC3,0X6D,0X37,0X2F,0X71, +0XFF,0XFF,0XED,0XF2,0X3F,0XFF,0XFE,0XF3,0X4B,0X1C,0X97,0X3C,0XD2,0XE8,0XD0,0X8E, +0XFF,0XDF,0X37,0XFD,0XCF,0XFF,0XEB,0XFD,0XB4,0XE3,0X68,0XCB,0X2D,0X17,0X6E,0X71, +0XFF,0XBF,0XD0,0XF2,0X3F,0XFF,0XFE,0XF6,0XCB,0X1C,0X97,0X34,0X52,0XE8,0X91,0X8E, +0XFF,0XDF,0XFF,0XFE,0XDF,0XFF,0XEB,0XFB,0X34,0XC3,0X68,0XCB,0X8B,0X97,0X6A,0X71, +0XFF,0XEF,0XC3,0XF3,0XAF,0XFF,0XFD,0XF5,0XCB,0X3C,0X97,0X34,0X4C,0X68,0X95,0X8E, +0XFF,0X3F,0XFD,0XFF,0XFF,0XFF,0XF7,0XEA,0XB4,0XC1,0X48,0XCB,0X13,0XB7,0X6A,0X71, +0XFF,0XDF,0XE7,0XEF,0XFF,0XFF,0XFC,0XF5,0X4B,0XBC,0X37,0X34,0XCC,0X4E,0X95,0X8E, +0XFF,0X3D,0XFB,0XF3,0XDF,0XFF,0XCB,0XEA,0XB4,0X42,0XC8,0XCB,0X13,0XB3,0X68,0X71, +0XFF,0XDF,0XEF,0XEF,0XFF,0XFF,0XFF,0XF5,0X5B,0XB9,0X27,0X34,0X0D,0X5D,0X92,0X8E, +0XFE,0XBD,0XF3,0XFF,0XBF,0XFF,0XF5,0XDA,0XA4,0X44,0XD0,0XC5,0X92,0XAE,0X6C,0X71, +0XFF,0X5F,0XCD,0XD7,0XFF,0XFF,0XCF,0XB5,0X5B,0XB3,0X2F,0X32,0X4D,0X75,0X92,0X8A, +0XFE,0XBC,0XB6,0XAB,0XBF,0XFF,0XF0,0XCA,0XA4,0X04,0XD0,0XC1,0X32,0X8A,0X6D,0X75, +0XFF,0X5F,0XDF,0XFF,0XDF,0XFF,0X5F,0X75,0X5A,0X81,0X2F,0X00,0X8D,0X75,0X92,0X8A, +}; diff --git a/main/img2.h b/main/img2.h new file mode 100644 index 0000000..744ccdf --- /dev/null +++ b/main/img2.h @@ -0,0 +1,298 @@ +const unsigned char gImage_img2[4736] = { /* 0X01,0X01,0X28,0X01,0X80,0X00, */ +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X7F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X7F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X01,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X01,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X03,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X03,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X07,0XFF,0XF6,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X04,0X14,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XD0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XDF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0X1F,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XF0,0X01,0X1F,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X07,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X07,0XC0,0X02,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X88,0X00,0X00,0X03,0XC0,0X38,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X80,0X00,0X00,0X07,0X80,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X00,0X00,0X00,0X03,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X00,0X00,0X00,0X07,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFE,0X00,0X00,0X00,0X01,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X02,0X22,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X07,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X3F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X3F,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X3F,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X7E,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X01,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFC,0X00,0X00,0X00,0X03,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFE,0X00,0X00,0X00,0X03,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFE,0X00,0X00,0X00,0X03,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFE,0X00,0X00,0X00,0X07,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X00,0X00,0X00,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0X80,0X00,0X0F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XC0,0X00,0X0F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XE0,0X00,0X3F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XF8,0X00,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFE,0X1F,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X03, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X0F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X7F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XA2,0XAF,0X80,0X00,0X02,0XAF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF7,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X9F,0XFF,0XFF,0XE7,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X0F,0XFF,0XFF,0XE3,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X0F,0XFF,0XFF,0XE1,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X07,0XFF,0XFF,0XE1,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X0F,0XFF,0XFF,0XF1,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X07,0XFF,0XFF,0XF0,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X07,0XFF,0XFF,0XF0,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X03,0XFF,0XFF,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X03,0XFF,0XFF,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X03,0XFF,0XFC,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X01,0XFF,0XF0,0X00,0X7F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X01,0XFF,0XE0,0X00,0X3F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X01,0XFF,0XC0,0X00,0X7F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0XFF,0X80,0X00,0X3F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF2,0X00,0X00,0XFF,0X80,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0XF0,0X00,0X0F,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X0F,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X07,0X00,0X00,0X00,0X00,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X0F,0X80,0X00,0X00,0X03,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XF5,0XFF,0XFF,0XFF,0XC0,0X04,0X00,0X00,0X00,0X03,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X40,0X7F,0XFF,0X55,0X40,0X00,0X00,0X00,0X00,0X0F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X00,0X1F,0XF4,0X00,0X00,0X00,0X00,0X00,0X00,0X0F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X00,0X1F,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X3F,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X00,0X1C,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X03,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X0F,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X7F,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X0F,0XFF,0XFC,0XFF, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0XFF,0XFC,0XFF, +0XFF,0XFF,0XFF,0XFF,0X00,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X7F,0XFC,0X7F, +0XFF,0XFF,0XFF,0XFF,0XAB,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X1F,0XFC,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X03,0XF8,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XF8,0X7F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X1B,0X00,0X00,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X07,0XFF,0XE8,0X00,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X80,0X00,0X00,0X5F,0XFE,0X00,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X07,0XFF,0X80,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X80,0X00,0X07,0XFF,0XF0,0X00,0X00,0X7F, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X07,0XFF,0XFF,0X00,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X8F,0XFF,0XFF,0XE8,0X00,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFD,0XFF,0XFF,0XFF,0XFF,0XAA,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0X9F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0X8F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XE5,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X0F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X03,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X3F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X3F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X0F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X0F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X07,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X03,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X03,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X01,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X01,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X01,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XF3,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XC1,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XC0,0X01,0XF7,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X7F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0X80,0X00,0XC3,0XFF,0XFF,0XFF,0X00,0X00,0X00,0X3F,0XFF,0XFF,0X37,0XFF,0XFF,0X7F, +0X80,0X00,0X01,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X75, +0X00,0X00,0X01,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40, +0X00,0X00,0X00,0X7F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X20, +0X00,0X00,0X00,0X0F,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40, +0X00,0X00,0X00,0X07,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X03,0XF0,0X00,0X00,0X2D,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X00,0X00,0X00,0X03,0XE0,0X00,0X0B,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X00,0X00,0X00,0X03,0XC0,0X0B,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X20, +0X00,0X00,0X00,0X03,0X83,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X03,0XEF,0XFF,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X00,0XF8,0XD0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X00,0X00,0X00,0X00,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40, +0X00,0X00,0X00,0X00,0X78,0X00,0X00,0X22,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X00,0XF8,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X00,0XFE,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X00,0X00,0X00,0X03,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X40, +0X00,0X00,0X00,0X03,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X0F,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X60, +0X00,0X00,0X00,0X0F,0XFD,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X80,0X00,0X00,0X3F,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XC0,0X00,0X00,0X3F,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XE0,0X00,0X00,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XF0,0X00,0X20,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFE,0XCB,0XFB,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XA0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X03,0X80, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X03,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X00,0X00,0X02,0XFF,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X80,0X00,0X00,0X00,0X2A,0XAF,0XFF,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XAA,0XAA,0XAF,0XFF,0XFF,0XFE,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFE,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00, +0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFC,0X00,0X00,0X00,0X00, +}; diff --git a/main/img3.h b/main/img3.h new file mode 100644 index 0000000..af57b05 --- /dev/null +++ b/main/img3.h @@ -0,0 +1,298 @@ +const unsigned char gImage_img3[4736] = { /* 0X01,0X01,0X28,0X01,0X80,0X00, */ +0X20,0XC4,0X34,0X40,0X44,0X40,0X00,0X00,0X0F,0XFF,0XCC,0XAC,0X00,0X00,0X00,0X00, +0X9E,0X3B,0X8B,0XBF,0XBB,0X10,0X00,0X00,0X03,0XF0,0X37,0X50,0X00,0X00,0X00,0X00, +0X61,0X84,0X70,0X40,0X44,0XC0,0X00,0X00,0X0F,0XEF,0XC8,0XA8,0X00,0X00,0X00,0X00, +0X0A,0X72,0X8F,0X15,0X12,0X00,0X00,0X00,0X03,0XFE,0XBF,0X44,0X00,0X00,0X00,0X00, +0XB4,0X8D,0X60,0XEA,0XE9,0X00,0X00,0X00,0X0F,0XD3,0XE0,0XB2,0X00,0X00,0X00,0X00, +0X4B,0X32,0X1D,0X14,0X16,0XC0,0X00,0X00,0X03,0XEE,0X9F,0X48,0X00,0X00,0X00,0X00, +0X24,0XC9,0XA2,0XC3,0XC8,0X00,0X00,0X00,0X0F,0XFB,0X60,0XA0,0X00,0X00,0X00,0X00, +0X91,0X16,0X4C,0X3C,0X33,0X00,0X00,0X00,0X03,0XFF,0XDE,0X58,0X00,0X00,0X00,0X00, +0X6E,0XE9,0XB3,0XC3,0X4C,0X00,0X00,0X00,0X0F,0XFD,0X63,0XA0,0X00,0X00,0X00,0X00, +0X00,0X04,0X4C,0X28,0X92,0X80,0X00,0X00,0X07,0XF2,0X9D,0X4C,0X00,0X00,0X00,0X00, +0XB7,0XFB,0X23,0X56,0X69,0X00,0X00,0X00,0X0B,0XFF,0XF2,0XB0,0X00,0X00,0X00,0X00, +0X48,0X04,0XD8,0XA9,0X94,0X00,0X00,0X00,0X0F,0XFA,0XCF,0X44,0X00,0X00,0X00,0X00, +0X23,0X6A,0X27,0X12,0X42,0X00,0X00,0X00,0X03,0XFF,0XBD,0XF0,0X00,0X00,0X00,0X00, +0X9C,0X95,0XC8,0XED,0X38,0X00,0X00,0X00,0X0F,0XFF,0XF2,0X00,0X00,0X00,0X00,0X00, +0X61,0X2A,0X35,0X12,0XC4,0X00,0X00,0X00,0X0F,0XFF,0X4F,0XE8,0X00,0X00,0X00,0X00, +0X0E,0XD1,0X4A,0XC9,0X30,0X00,0X00,0X00,0X03,0XFF,0XF8,0X30,0X00,0X00,0X00,0X00, +0XB1,0X2E,0XA4,0X36,0X8A,0X00,0X00,0X00,0X0F,0XFF,0X2F,0XDC,0X00,0X00,0X00,0X00, +0X4A,0XD1,0X5B,0XC9,0X70,0X00,0X00,0X00,0X0B,0XFF,0XF2,0XA0,0X00,0X00,0X00,0X00, +0X14,0X0C,0XA4,0X24,0X8C,0X00,0X00,0X00,0X0F,0XFF,0XAF,0XD0,0X00,0X00,0X00,0X00, +0XEB,0XF3,0X13,0X5B,0X60,0X00,0X00,0X00,0X0B,0XFF,0XDA,0X28,0X00,0X00,0X00,0X00, +0X04,0X0C,0XEC,0XA4,0X1A,0X00,0X00,0X00,0X0F,0XFF,0XED,0XD0,0X00,0X00,0X00,0X00, +0XB3,0XD3,0X13,0X13,0XE0,0X00,0X00,0X00,0X0B,0XFF,0X33,0X28,0X00,0X00,0X00,0X00, +0X48,0X28,0XA8,0XEC,0X0C,0X00,0X00,0X00,0X0F,0XF2,0XCC,0XD2,0X80,0X00,0X00,0X00, +0X36,0XD7,0X57,0X01,0X70,0X00,0X00,0X00,0X0B,0XFF,0XB3,0X2C,0X00,0X00,0X00,0X00, +0X89,0X28,0XA8,0XFE,0X85,0X00,0X00,0X00,0X0F,0XFF,0XDD,0XF2,0X00,0X00,0X00,0X00, +0X66,0XC6,0X46,0X01,0X38,0X00,0X00,0X00,0X0F,0XFF,0XF7,0X0A,0XC0,0X00,0X00,0X00, +0X11,0X39,0XB9,0XBA,0XC4,0X00,0X00,0X00,0X0F,0XFD,0X5D,0XF5,0X00,0X00,0X00,0X00, +0XAC,0XC6,0X46,0X45,0X33,0X00,0X00,0X00,0X0F,0XFA,0XA2,0X08,0X80,0X00,0X00,0X00, +0X52,0X31,0XB1,0XAA,0XC8,0X00,0X00,0X00,0X0F,0XFF,0XFD,0XB6,0X00,0X00,0X00,0X00, +0X0D,0XCC,0X4C,0X54,0X34,0X00,0X00,0X00,0X0F,0XFF,0XEE,0XEB,0X80,0X00,0X00,0X00, +0XE2,0X33,0X33,0X2B,0XC3,0X00,0X00,0X00,0X0F,0XFF,0XB3,0X14,0X00,0X00,0X00,0X00, +0X1C,0XCC,0XCC,0XC4,0X38,0X00,0X00,0X00,0X0F,0XFF,0XDA,0XEB,0X00,0X00,0X00,0X00, +0X43,0X33,0X33,0X3B,0X44,0X00,0X00,0X00,0X0F,0XFF,0XED,0X34,0X00,0X00,0X00,0X00, +0XB4,0XCC,0XCC,0XC4,0XAA,0X00,0X00,0X00,0X0F,0XFF,0XF6,0XCA,0X00,0X00,0X00,0X00, +0X0B,0X33,0X33,0X33,0X51,0X00,0X00,0X00,0X0F,0XFF,0XEA,0XB5,0X80,0X00,0X00,0X00, +0XF0,0XCC,0XCC,0XCC,0XAC,0X80,0X00,0X00,0X0F,0XFF,0XFD,0X48,0X00,0X00,0X00,0X00, +0X0F,0X33,0X33,0X33,0X12,0X00,0X00,0X00,0X0F,0XFF,0XEE,0XB6,0XC0,0X00,0X00,0X00, +0X50,0XCC,0XCC,0XCC,0XE8,0X80,0X00,0X00,0X0F,0XFB,0XFD,0X40,0X00,0X00,0X00,0X00, +0XAB,0X33,0X33,0X33,0X16,0X00,0X00,0X00,0X0F,0XFC,0XD6,0XBB,0X00,0X00,0X00,0X00, +0X14,0XCC,0XCC,0XCC,0XC9,0X00,0X00,0X00,0X0F,0XFF,0XBB,0X44,0X80,0X00,0X00,0X00, +0XCA,0X32,0X33,0X33,0X32,0X80,0X00,0X00,0X0F,0XFF,0XE5,0XB0,0X00,0X00,0X00,0X00, +0X35,0XCD,0XCC,0XCC,0XCC,0X40,0X00,0X00,0X0F,0XFF,0XFE,0X48,0X00,0X00,0X00,0X00, +0X82,0X32,0X33,0X33,0X33,0X00,0X00,0X00,0X0F,0XFF,0XF3,0XB6,0X00,0X00,0X00,0X00, +0X7C,0XCD,0XCC,0XCC,0XCC,0X80,0X00,0X00,0X0F,0XFF,0XFF,0X40,0X00,0X00,0X00,0X00, +0X83,0X22,0X33,0X33,0X32,0X40,0X00,0X00,0X0F,0XFF,0XFD,0X1A,0X00,0X00,0X00,0X00, +0X2C,0XDD,0XCC,0XCC,0XCD,0X00,0X00,0X00,0X0F,0XFF,0XE4,0XE4,0X00,0X00,0X00,0X00, +0XD3,0X22,0X33,0X33,0X32,0X80,0X00,0X00,0X0F,0XFF,0XDA,0X12,0X00,0X00,0X00,0X00, +0X28,0XD9,0XCC,0XCC,0XCC,0X40,0X00,0X00,0X0F,0XFF,0XAB,0XA8,0X00,0X00,0X00,0X00, +0X97,0X26,0X23,0X33,0X33,0X20,0X00,0X00,0X0F,0XFC,0XFC,0XD4,0X00,0X00,0X00,0X00, +0X48,0XD9,0XDC,0XCC,0XCC,0X80,0X00,0X00,0X0F,0XFF,0XFF,0X4A,0X00,0X00,0X00,0X00, +0XB7,0X26,0X23,0X33,0X33,0X40,0X00,0X00,0X07,0XFF,0XF3,0X21,0X00,0X00,0X00,0X00, +0X00,0XD9,0XDC,0XCC,0XCC,0X20,0X00,0X00,0X0F,0XFF,0XFC,0XBC,0X00,0X00,0X00,0X00, +0XFF,0X26,0X23,0X33,0X33,0X90,0X00,0X00,0X0B,0XFF,0XC2,0XC1,0X00,0X00,0X00,0X00, +0X00,0XD9,0XDC,0XCC,0XCC,0X48,0X00,0X00,0X0F,0XFF,0XFF,0X5C,0X00,0X00,0X00,0X00, +0XB7,0X26,0X23,0X33,0X33,0X23,0X00,0X00,0X0F,0XFF,0XEB,0XA0,0X00,0X00,0X00,0X00, +0X48,0XD9,0XDC,0XCC,0XCC,0XD0,0X00,0X00,0X03,0XFF,0X15,0X5A,0X00,0X00,0X00,0X00, +0XB7,0X26,0X23,0X33,0X33,0X0C,0X80,0X00,0X0F,0XFF,0XEA,0X84,0X00,0X00,0X00,0X00, +0X00,0X99,0XDC,0XCC,0XCC,0XE2,0X40,0X00,0X0F,0XFF,0X3B,0X7A,0X00,0X00,0X00,0X00, +0XFF,0X66,0X23,0X33,0X33,0X19,0X00,0X00,0X03,0XFE,0XDE,0X84,0X00,0X00,0X00,0X00, +0X00,0X99,0XDC,0XCC,0XCC,0XC4,0XB0,0X00,0X0F,0XFF,0XEB,0XFB,0X00,0X00,0X00,0X00, +0XB7,0X66,0X23,0X33,0X33,0X33,0X40,0X00,0X0F,0XFF,0XF4,0X10,0X00,0X00,0X00,0X00, +0X48,0X99,0XDC,0XCC,0XCC,0XCC,0X00,0X00,0X03,0XFF,0XFB,0XCC,0X00,0X00,0X00,0X00, +0XB6,0X66,0X23,0X33,0X33,0X21,0XB0,0X00,0X0F,0XFF,0XDC,0XA3,0X00,0X00,0X00,0X00, +0X01,0X99,0XDC,0XCC,0XCC,0XDE,0X40,0X00,0X0F,0XFA,0XF7,0X7C,0X00,0X00,0X00,0X00, +0XFE,0X66,0X23,0X33,0X33,0X21,0X10,0X00,0X07,0XFF,0X3D,0X90,0X00,0X00,0X00,0X00, +0X01,0X99,0XDC,0XCC,0XCC,0XCC,0XC8,0X00,0X0B,0XFF,0XCF,0XC5,0X00,0X00,0X00,0X00, +0XB6,0X66,0X23,0X33,0X33,0X32,0X24,0X00,0X0F,0XFF,0XF1,0X40,0X00,0X00,0X00,0X00, +0X49,0X99,0XDC,0XCC,0XCC,0XC5,0X90,0X00,0X07,0XFF,0X2E,0X20,0X00,0X00,0X00,0X00, +0XB4,0X66,0X23,0X33,0X33,0X3A,0X4A,0X00,0X0F,0XFE,0XF9,0XC0,0X00,0X00,0X00,0X00, +0X0B,0X99,0XDC,0XCC,0XCC,0XC4,0XA4,0X00,0X17,0XFF,0XBF,0X24,0X00,0X00,0X00,0X00, +0XE4,0X66,0X23,0X33,0X33,0X33,0X11,0X00,0X0F,0XFF,0XC4,0XB8,0X00,0X00,0X00,0X00, +0X1B,0X99,0XDC,0XCC,0XCC,0XC8,0XC4,0X00,0X07,0XFF,0XFA,0X80,0X00,0X00,0X00,0X00, +0XA4,0X66,0X23,0X33,0X33,0X36,0X32,0X80,0X1F,0XFF,0XDA,0XFA,0X00,0X00,0X00,0X00, +0X5B,0X99,0XDC,0XCC,0XCC,0XC9,0X48,0X20,0X03,0XFF,0X3F,0X40,0X00,0X00,0X00,0X00, +0XA4,0X66,0X23,0X33,0X33,0X32,0X92,0XC8,0X0F,0XFF,0XFC,0XAE,0XC0,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCC,0X69,0X00,0X33,0XFD,0XF3,0XF1,0X00,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X33,0X04,0X34,0X8F,0XFF,0XDF,0X4C,0X60,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XC8,0XB2,0XC2,0X37,0XFF,0XED,0XF3,0X80,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X37,0X49,0X18,0XCB,0XFF,0XF6,0X0C,0X40,0X00,0X00,0X00, +0X53,0X99,0XDC,0XCC,0XCC,0XC8,0X24,0X43,0X3F,0XFF,0X2B,0XE3,0X20,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X33,0X93,0X28,0XCF,0XFC,0XFD,0X18,0X80,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCC,0X6C,0XD2,0X33,0XFF,0XD2,0XA2,0X00,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X31,0X01,0X09,0XEF,0XFF,0X6F,0XDC,0X00,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCE,0XB4,0X64,0X3F,0XFD,0XFE,0XE3,0X80,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X31,0X4B,0X12,0XFF,0XFA,0XFB,0X3C,0X40,0X00,0X00,0X00, +0X53,0X99,0XDC,0XCC,0XCC,0XCC,0X24,0XCD,0X3F,0XFF,0XFF,0XD2,0X00,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X33,0X91,0X20,0XFF,0XFF,0XFC,0XAF,0XC0,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCC,0X4C,0X56,0X3F,0XFF,0XBB,0XF5,0X00,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X31,0X33,0X09,0XFF,0XFF,0XFF,0XFC,0X30,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCE,0XC8,0XE2,0X2F,0XFF,0XEF,0X02,0X40,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X31,0X26,0X0C,0XF7,0XFF,0XFD,0XFA,0X80,0X00,0X00,0X00, +0X53,0X99,0XDC,0XCC,0XCC,0XCC,0X90,0XB3,0X3F,0XFF,0XEA,0XAD,0X48,0X00,0X00,0X00, +0XAC,0X66,0X23,0X33,0X33,0X32,0X4B,0X48,0XD7,0XFD,0XFD,0X52,0XB0,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCD,0X30,0X25,0X0D,0X40,0XFE,0XAD,0X40,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X32,0XCB,0X98,0X00,0X00,0XFB,0X52,0XA8,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCC,0X24,0X40,0X00,0X02,0X7D,0XCD,0X30,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X33,0X91,0X00,0X00,0X20,0X7E,0XEA,0XE8,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCC,0X4C,0X00,0X00,0X00,0X15,0X3F,0XB0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X31,0X32,0X00,0X00,0X00,0X0E,0XD0,0X50,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCE,0XC8,0X00,0X00,0X00,0X0B,0X3D,0XF0,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X31,0X20,0X00,0X00,0X00,0X0C,0XCA,0X08,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCC,0X9C,0X00,0X00,0X00,0X03,0XE3,0XF0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X33,0X40,0X00,0X00,0X00,0X00,0X7D,0X40,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCC,0XA8,0X00,0X00,0X00,0X00,0X00,0X70,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X32,0X50,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCD,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X30,0X10,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XD0,0X00,0X00,0X00,0X00,0X00,0X35,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XC8,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X37,0X08,0X00,0X00,0X00,0X00,0X0C,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC8,0XA0,0X00,0X00,0X00,0X0C,0XB3,0X80,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X37,0X5C,0X00,0X00,0X00,0X3E,0XDC,0X40,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC8,0X80,0X00,0X00,0X00,0XCF,0XB7,0XB0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0X70,0X00,0X00,0X00,0XF3,0XDD,0X40,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X8C,0X00,0X00,0X23,0XFD,0X40,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0X62,0X00,0X00,0X35,0X56,0XAB,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X19,0X00,0X00,0X02,0XBB,0XFC,0X20,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0XA4,0X00,0X00,0X01,0XFF,0X10,0XC8,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X4B,0XA0,0X00,0X00,0X54,0XE3,0X20,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0XB0,0X0C,0X3B,0X81,0XEB,0X0C,0XD0,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X0E,0XD2,0XCF,0XFC,0X7C,0X3F,0X48,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0XE1,0X29,0X35,0XF1,0X10,0XD1,0X20,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X14,0X94,0X00,0X00,0X03,0X6F,0XD8,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0XAB,0X40,0X00,0X00,0X03,0XD0,0X02,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC9,0X40,0X00,0X00,0X00,0X0D,0X6F,0XEC,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X36,0XBC,0X00,0X00,0X00,0X1A,0XDB,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC1,0X00,0X00,0X00,0X00,0X0F,0X2C,0XE0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X26,0XD0,0X00,0X00,0X00,0X0D,0XF7,0X50,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC1,0X20,0X00,0X00,0X00,0X03,0X50,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X26,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC1,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X20,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XC0,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X00,0X00,0X00,0X00,0X00,0X00,0X0D,0X70,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X20,0X80,0X00,0X00,0X00,0X00,0XBA,0X0C,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X00,0X00,0X00,0X00,0X02,0XED,0XF0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XA0,0X00,0X00,0X00,0X07,0X96,0X8C,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X40,0X00,0X00,0X00,0X04,0XFB,0X72,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XA0,0X00,0X00,0X00,0X07,0X44,0XC4,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X00,0X00,0X00,0X00,0X0C,0XFB,0X35,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XF0,0X00,0X01,0XA8,0X03,0X4D,0XD2,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X00,0X00,0X01,0X7F,0XBE,0XB2,0X00,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XD0,0X00,0X00,0XF5,0XF3,0XCD,0XCF,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0X28,0X00,0X03,0XFC,0X1E,0XB2,0X50,0X80,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X30,0XC0,0X00,0X00,0X00,0X0B,0X5F,0X2E,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0X3C,0X00,0X00,0X00,0X1D,0XA0,0XB3,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X82,0X80,0X00,0X00,0X0A,0XDF,0X4C,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X69,0X48,0X00,0X00,0X37,0X68,0XB3,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X94,0X32,0X00,0XB2,0XBD,0XB7,0X4C,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0X2B,0XCD,0X0D,0XFF,0XD6,0XC8,0XB2,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X30,0XC4,0X20,0XD6,0XCC,0XEB,0X3F,0X4C,0X80,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0X32,0X97,0X2B,0XB3,0X3D,0XC5,0XB3,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X89,0X48,0XD4,0XFF,0XD6,0X7A,0XCC,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X64,0XB2,0X2F,0X6C,0XEB,0XAD,0X32,0X80,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X1A,0X4C,0XD2,0XB3,0X3D,0X56,0XCC,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0XC1,0X13,0X2D,0XDE,0XD6,0XFB,0X32,0X00,0X00,0X00, +0XEC,0X66,0X23,0X33,0X33,0X30,0X2C,0XC8,0XD7,0X7B,0XAB,0X4D,0XCC,0X00,0X00,0X00, +0X13,0X99,0XDC,0XCC,0XCC,0XCF,0X53,0X26,0X28,0XCC,0XFD,0XB2,0X32,0X80,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X88,0X91,0XD7,0X77,0X56,0XDD,0XCD,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X66,0X6C,0X2D,0XBD,0XBB,0X62,0X30,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0X11,0X02,0X92,0XD6,0XCD,0XBF,0X8E,0X80,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0XCA,0XB5,0X6F,0X6B,0X76,0XC8,0XE1,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0X24,0X48,0X91,0XBC,0XBB,0X37,0X1C,0X00,0X00,0X00, +0X73,0X99,0XDC,0XCC,0XCC,0XCF,0X53,0X26,0X2E,0XD7,0XCD,0XEC,0XE3,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0XA8,0X91,0XD3,0X6A,0X72,0X93,0X1C,0X80,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X06,0X4C,0X2D,0XBD,0XDD,0X6C,0XE2,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XF0,0XB2,0XD2,0XD7,0X37,0XBB,0X1D,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X0B,0X04,0X2F,0X6C,0XD8,0XC4,0XE0,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0X54,0XDB,0X51,0XB3,0X6F,0X3B,0X1E,0X00,0X00,0X00, +0X73,0X99,0XDC,0XCC,0XCC,0XCF,0XA2,0X20,0XAE,0XDD,0XB5,0XEC,0XE1,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0X59,0X8D,0X13,0X76,0XDA,0X13,0X1E,0X80,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X04,0X32,0XEC,0XDB,0X2F,0XFE,0XE1,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XF2,0XC8,0X13,0X2C,0XF1,0X41,0X14,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X09,0X27,0X6D,0XF7,0X8E,0XBF,0XEB,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0XD6,0X90,0X92,0XDA,0X75,0X50,0X10,0X00,0X00,0X00, +0X73,0X99,0XDC,0XCC,0XCC,0XCF,0X20,0X4A,0X6F,0X2D,0XDB,0XAF,0XE8,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0XDB,0X25,0X10,0XF7,0X34,0XF4,0X14,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X04,0X92,0XAF,0XB8,0XCF,0X0B,0XE0,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X30,0XF1,0X28,0X52,0XCF,0X72,0XF5,0X1A,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCF,0X0E,0X93,0X2D,0X7A,0X9D,0X5A,0XE4,0X80,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0XD0,0X4C,0X92,0XAF,0XEA,0XA5,0X00,0X00,0X00,0X00, +0X73,0X99,0XDC,0XCC,0XCC,0XCF,0X2B,0X21,0X6F,0XD2,0X57,0X5A,0XF3,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X30,0XC4,0X94,0X10,0X7D,0XB9,0XAD,0X0C,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCE,0X3A,0X49,0XAF,0XD3,0X4E,0XF3,0X71,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X31,0X81,0XB0,0X51,0X2C,0XF5,0X0C,0X8A,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCE,0X6C,0X00,0X00,0XFB,0X2B,0XF7,0X74,0X80,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X31,0X92,0XC0,0X00,0X4E,0XD4,0X28,0X8B,0X00,0X00,0X00, +0X73,0X99,0XDC,0XCC,0XCC,0XCC,0X29,0X20,0X00,0X73,0X7F,0XD7,0X70,0X00,0X00,0X00, +0X8C,0X66,0X23,0X33,0X33,0X33,0XC4,0X40,0X00,0X5D,0X01,0X28,0X88,0X00,0X00,0X00, +0X33,0X99,0XDC,0XCC,0XCC,0XCC,0X33,0X30,0X00,0XB0,0X00,0X17,0X72,0X00,0X00,0X00, +0XCC,0X66,0X23,0X33,0X33,0X32,0X8C,0X80,0X01,0X50,0X00,0X28,0X89,0X00,0X00,0X00, +0X33,0X19,0XDC,0XCC,0XCC,0XCD,0X62,0X50,0X06,0XC0,0X00,0X17,0X64,0X00,0X00,0X00, +0X8C,0XE6,0X23,0X33,0X33,0X32,0X99,0X00,0X01,0X40,0X00,0X00,0X12,0X00,0X00,0X00, +0X73,0X19,0XDC,0XCC,0XCC,0XCC,0X44,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X8C,0XE6,0X23,0X33,0X33,0X33,0X31,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X19,0XDC,0XCC,0XCC,0XCC,0XC0,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XC4,0XE6,0X23,0X33,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X3B,0X19,0XDC,0XCC,0XCC,0XCC,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X84,0XE6,0X23,0X33,0X33,0X32,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X73,0X19,0XDC,0XCC,0XCC,0XCD,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X8C,0XC6,0X23,0X33,0X33,0X32,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X33,0X39,0XDC,0XCC,0XCC,0XCC,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XC4,0XC6,0X23,0X33,0X33,0X33,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X20,0X00,0X00, +0X3B,0X39,0XDC,0XCC,0XCC,0XCC,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X80,0X00,0X00, +0X84,0XC6,0X23,0X33,0X33,0X32,0X00,0X00,0X00,0X00,0X00,0X00,0X01,0X40,0X00,0X00, +0X73,0X39,0XDC,0XCC,0XCC,0XCD,0X00,0X00,0X00,0X00,0X00,0X00,0X0C,0X20,0X00,0X00, +0X8C,0XC6,0X23,0X33,0X33,0X32,0X80,0X00,0X00,0X00,0X00,0X01,0X43,0X00,0X00,0X00, +0X33,0X39,0X9C,0XCC,0XCC,0XCC,0X00,0X00,0X00,0X00,0X00,0X06,0XBC,0X00,0X00,0X00, +0XCC,0XC6,0X63,0X33,0X33,0X33,0XA0,0X00,0X00,0X00,0X00,0X19,0X43,0X00,0X00,0X00, +0X32,0X39,0X9C,0XCC,0XCC,0XCC,0X50,0X00,0X00,0X00,0X00,0X06,0XBC,0X80,0X00,0X00, +0X8D,0XC6,0X43,0X33,0X33,0X32,0XAB,0X20,0X00,0X80,0X00,0X19,0X43,0X00,0X00,0X00, +0X52,0X39,0XBC,0XCC,0XCC,0XCD,0X10,0X80,0X00,0X00,0X00,0X06,0XBC,0X80,0X00,0X00, +0XAC,0XC6,0X42,0X33,0X33,0X32,0XCA,0X40,0X00,0X00,0X00,0X19,0X43,0X00,0X00,0X00, +0X13,0X39,0XBD,0XCC,0XCC,0XCC,0X25,0X20,0X00,0X03,0X1D,0X66,0XBC,0XC0,0X00,0X00, +0XCC,0XC6,0X42,0X33,0X33,0X33,0XD0,0X10,0X00,0X00,0XE1,0X99,0X43,0X00,0X00,0X00, +0X33,0X31,0XBD,0XCC,0XCC,0XCC,0X0A,0XC0,0X00,0X00,0X04,0X66,0XA8,0X00,0X00,0X00, +0X88,0XCE,0X42,0X33,0X33,0X31,0X65,0X10,0X08,0X00,0X01,0XB9,0X53,0XC0,0X00,0X00, +0X57,0X31,0XB9,0X8C,0XCC,0XCE,0X90,0X4B,0X27,0X80,0X00,0X46,0X80,0X00,0X00,0X00, +0XA8,0XCC,0X46,0X73,0X33,0X31,0X4D,0X24,0XD8,0X7A,0XF3,0XB9,0X6B,0X00,0X00,0X00, +0X13,0X33,0XB9,0X8C,0XCC,0XCC,0X30,0X90,0X07,0XAD,0X0C,0X46,0X90,0X80,0X00,0X00, +0XCC,0XCC,0X46,0X73,0X33,0X33,0XC4,0X43,0X38,0X56,0XF3,0XB9,0X6B,0X00,0X00,0X00, +0X33,0X33,0X31,0X8C,0XCC,0XCC,0X1B,0X2C,0XC7,0XA9,0X0D,0X46,0X84,0X40,0X00,0X00, +0X88,0XCC,0XCC,0X73,0X33,0X31,0X60,0X90,0X18,0XF6,0XF2,0XB9,0X71,0X00,0X00,0X00, +0X57,0X33,0X33,0X0C,0XCC,0XCE,0X8A,0X43,0X47,0X1B,0X0D,0X46,0X80,0X00,0X00,0X00, +0XA8,0XCC,0XCC,0XD3,0X33,0X31,0X35,0X2C,0X28,0XE4,0XF2,0XB9,0X50,0X00,0X00,0X00, +0X13,0X33,0X33,0X2C,0XCC,0XCC,0XC8,0X81,0X57,0X3B,0X0D,0X46,0XA8,0X00,0X00,0X00, +0XCC,0XCC,0XCC,0XD3,0X33,0X33,0X22,0X34,0X08,0XCC,0XF2,0XB9,0X54,0X00,0X00,0X00, +0X31,0X33,0X33,0X2C,0XCC,0XCC,0X9C,0XC0,0X37,0X73,0X4D,0X46,0XA0,0X00,0X00,0X00, +0X8E,0XCC,0XCC,0X93,0X33,0X31,0X43,0X00,0X00,0X1D,0X72,0XB9,0X5A,0X80,0X00,0X00, +0X61,0X33,0X33,0X6C,0XC8,0XCE,0XA8,0X00,0X00,0X01,0X0D,0X46,0XA4,0X00,0X00,0X00, +0X1C,0XCC,0XCC,0X93,0X37,0X30,0X52,0X00,0X00,0X00,0X06,0XB9,0X53,0X00,0X00,0X00, +0XA3,0X33,0X32,0X4C,0XC8,0XCF,0X0C,0X00,0X00,0X00,0X01,0X46,0XA8,0X80,0X00,0X00, +0X4C,0XCC,0XCD,0XB3,0X37,0X30,0XE0,0X00,0X00,0X00,0X06,0XB9,0X53,0X40,0X00,0X00, +0X32,0X31,0X32,0X4C,0XC0,0XCB,0X18,0X00,0X00,0X70,0X12,0XC6,0XAC,0X00,0X00,0X00, +0X85,0X8E,0XCD,0XA3,0X3F,0X34,0X40,0X00,0X00,0X00,0X03,0X39,0X51,0X00,0X00,0X00, +0X7A,0X71,0X10,0X58,0XC0,0X8B,0XA8,0X00,0X00,0X00,0X05,0XC6,0XAC,0X80,0X00,0X00, +0X04,0X8C,0XEF,0X27,0X37,0X70,0X10,0X00,0X00,0X00,0X00,0X79,0X52,0X00,0X00,0X00, +0XB3,0X33,0X10,0XD8,0X88,0X8F,0XC0,0X00,0X00,0X00,0X00,0X16,0XAD,0X00,0X00,0X00, +0X4C,0XCC,0XCF,0X07,0X77,0X30,0X30,0X00,0X00,0X00,0X00,0X01,0X00,0X00,0X00,0X00, +0X23,0X31,0X30,0XF0,0X80,0XCD,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X98,0XCE,0XCB,0X0D,0X3B,0X20,0X10,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X66,0X11,0X14,0XB2,0XC4,0X50,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X09,0XEC,0XEB,0X4D,0X3B,0XA0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XB2,0X13,0X10,0XB2,0XC4,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X4C,0XCC,0XCF,0X04,0X12,0XA0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X23,0X31,0X30,0XFB,0XED,0X40,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X94,0XCE,0XCD,0X04,0X12,0X20,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X6A,0X11,0X22,0XB2,0XC9,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X05,0XEA,0X5D,0X4D,0X36,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XB2,0X15,0XA0,0XB2,0XC9,0XE0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X4C,0XCA,0X4F,0X0C,0X24,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X23,0X31,0X30,0XE3,0XDA,0XB8,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X94,0XCE,0XCD,0X18,0X25,0X46,0XC0,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X6A,0X11,0X12,0XA6,0X92,0XB1,0X30,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0X05,0XEA,0XE9,0X59,0X6D,0X0A,0X80,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00, +0XB0,0X14,0X16,0X82,0X90,0XF4,0X68,0X00,0X00,0X00,0X00,0X00,0X0A,0X00,0X00,0X00, +0X4E,0XCB,0XC8,0X7C,0X4F,0X0B,0X10,0X00,0X00,0X00,0X00,0X00,0X20,0X00,0X00,0X00, +0X21,0X34,0X37,0X83,0XA0,0X50,0XA0,0X00,0X00,0X00,0X00,0X00,0X0A,0X00,0X00,0X00, +0X9A,0XC3,0X40,0X54,0X5B,0XAF,0X48,0X00,0X00,0X00,0X00,0X03,0XA4,0X00,0X00,0X00, +0X44,0X3C,0XBD,0X2B,0X24,0X50,0X20,0X00,0X00,0X60,0X0A,0X3C,0X40,0X00,0X00,0X00, +0X33,0X81,0X02,0XD0,0XCB,0X05,0X90,0X00,0X00,0X00,0X0B,0X81,0X00,0X00,0X00,0X00, +0X88,0X6E,0XED,0X0E,0X30,0XFA,0X48,0X00,0X00,0X00,0X04,0X68,0X00,0X00,0X00,0X00, +0X56,0X91,0X10,0XF1,0X8E,0X05,0X12,0X00,0X00,0X00,0X01,0X94,0X00,0X00,0X00,0X00, +0X21,0X2C,0XAE,0X0A,0X51,0XD0,0XCC,0X00,0X20,0X00,0X01,0X41,0X00,0X00,0X00,0X00, +0X9A,0XC3,0X41,0XD4,0XAA,0X2F,0X20,0XC0,0X0A,0X00,0X00,0X28,0X00,0X00,0X00,0X00, +0X44,0X38,0X3A,0X2B,0X55,0X40,0X4B,0X10,0X31,0XB0,0X6D,0X54,0X00,0X00,0X00,0X00, +0X33,0X47,0XC4,0XD0,0X00,0XBD,0X24,0X02,0X4E,0X4A,0X92,0X00,0X00,0X00,0X00,0X00, +0X88,0XA8,0X2B,0X0F,0XFF,0X02,0X90,0XC8,0X01,0XB5,0X6D,0XCB,0X00,0X00,0X00,0X00, +0X57,0X12,0X90,0XE0,0X00,0XEC,0X4B,0X22,0X96,0X4A,0X92,0X30,0X00,0X00,0X00,0X00, +0X20,0XED,0X6E,0X15,0X55,0X13,0X14,0X08,0X09,0XB5,0X6D,0X40,0X00,0X00,0X00,0X00, +0X9A,0X12,0X91,0XAA,0XAA,0XA8,0XC0,0XA2,0XA6,0X4A,0X90,0X88,0X00,0X00,0X00,0X00, +0X45,0X44,0X44,0X55,0X55,0X56,0X2A,0X08,0X49,0XB5,0X6F,0X70,0X00,0X00,0X00,0X00, +0X2A,0XBB,0X3B,0X00,0X00,0X01,0X40,0XA2,0X15,0X4A,0X90,0X00,0X00,0X00,0X00,0X00, +0X91,0X04,0XC4,0XFF,0XFF,0XFC,0XAA,0X09,0X44,0XB5,0X6A,0XC0,0X00,0X00,0X00,0X00, +0X4C,0XF3,0X33,0X00,0X00,0X03,0X00,0XA0,0X13,0X4A,0X95,0X20,0X00,0X00,0X00,0X00, +0X22,0X08,0X88,0XAA,0XAA,0XA8,0XAA,0X08,0X0A,0XB5,0X6A,0X88,0X00,0X00,0X00,0X00, +}; diff --git a/main/img_hacking.c b/main/img_hacking.c new file mode 100644 index 0000000..8ce5312 --- /dev/null +++ b/main/img_hacking.c @@ -0,0 +1,2382 @@ +#include + +/* + * original image found on https://www.flickr.com/photos/adulau/9464930917/ + * (Alexandre Dulaunoy) + * + * "Feel free to reuse, use and abuse my pictures under the CC-SA license, + * the GNU Free Documentation License or the GNU General Public License." + * + * image is cropped and resized to 296x128 pixels. + */ + +const uint8_t img_hacking[37888] = { + 0x83,0x8e,0x85,0x78,0x69,0x5f,0x4c,0x3e,0x33,0x37,0x3c,0x41,0x40,0x43,0x52,0x5e, + 0x6b,0x75,0x77,0x72,0x6c,0x6b,0x6d,0x75,0x89,0x90,0x8f,0x8d,0x7c,0x7d,0x7c,0x74, + 0x6b,0x61,0x5b,0x59,0x5a,0x5d,0x5e,0x5d,0x5c,0x5b,0x58,0x58,0x5a,0x5d,0x5d,0x5f, + 0x60,0x60,0x5f,0x66,0x70,0x72,0x6e,0x69,0x62,0x57,0x4e,0x4f,0x53,0x5a,0x5f,0x6d, + 0x8d,0xa7,0xad,0xa9,0xa0,0x8e,0x65,0x3f,0x32,0x2e,0x2d,0x2d,0x30,0x33,0x36,0x3c, + 0x3c,0x3b,0x3a,0x38,0x3a,0x3e,0x43,0x46,0x46,0x47,0x49,0x4c,0x4e,0x4d,0x4f,0x4c, + 0x48,0x44,0x42,0x43,0x41,0x3d,0x3b,0x38,0x33,0x2f,0x34,0x39,0x3d,0x44,0x44,0x3b, + 0x31,0x2f,0x2d,0x34,0x3c,0x3c,0x3a,0x37,0x2e,0x26,0x28,0x2b,0x30,0x3a,0x43,0x49, + 0x88,0x8d,0x86,0x78,0x6a,0x62,0x50,0x3d,0x31,0x32,0x3b,0x3e,0x43,0x41,0x44,0x51, + 0x5d,0x69,0x72,0x75,0x76,0x71,0x71,0x80,0x97,0x9c,0x99,0x93,0x7f,0x7f,0x7e,0x74, + 0x6a,0x60,0x58,0x5a,0x5e,0x61,0x63,0x65,0x63,0x62,0x60,0x5f,0x5f,0x62,0x63,0x65, + 0x65,0x65,0x62,0x62,0x68,0x71,0x72,0x6c,0x65,0x5e,0x55,0x52,0x56,0x5b,0x61,0x6d, + 0x78,0x8f,0xac,0xb0,0xab,0x9e,0x87,0x59,0x37,0x2e,0x31,0x2e,0x2f,0x34,0x36,0x3c, + 0x3f,0x40,0x3f,0x3e,0x3e,0x42,0x47,0x49,0x4b,0x4c,0x4c,0x4d,0x4d,0x50,0x4f,0x4d, + 0x4b,0x47,0x45,0x42,0x3f,0x3d,0x3c,0x38,0x32,0x31,0x32,0x35,0x37,0x39,0x39,0x33, + 0x2e,0x30,0x34,0x39,0x3d,0x3c,0x36,0x2e,0x2a,0x27,0x25,0x2d,0x35,0x3b,0x3f,0x4a, + 0x86,0x89,0x84,0x76,0x6b,0x65,0x50,0x3f,0x30,0x2f,0x38,0x3c,0x41,0x44,0x40,0x42, + 0x4f,0x5a,0x67,0x72,0x77,0x7a,0x75,0x7e,0x9e,0x9d,0x99,0x92,0x7f,0x7e,0x7b,0x72, + 0x64,0x5d,0x58,0x59,0x5d,0x63,0x67,0x69,0x68,0x67,0x66,0x65,0x64,0x66,0x67,0x68, + 0x68,0x68,0x66,0x67,0x66,0x69,0x72,0x73,0x71,0x6a,0x5a,0x54,0x59,0x5d,0x65,0x6b, + 0x7a,0x86,0x9a,0xaf,0xb1,0xa8,0x97,0x7e,0x4d,0x38,0x36,0x37,0x37,0x39,0x3b,0x40, + 0x43,0x45,0x42,0x42,0x43,0x48,0x4b,0x4c,0x4b,0x4a,0x47,0x48,0x4a,0x4c,0x4b,0x4d, + 0x50,0x48,0x42,0x3c,0x38,0x35,0x34,0x31,0x2d,0x2b,0x2e,0x2f,0x32,0x32,0x33,0x31, + 0x30,0x33,0x38,0x3e,0x3d,0x3b,0x38,0x32,0x2f,0x2c,0x29,0x2f,0x38,0x3d,0x42,0x49, + 0x80,0x89,0x85,0x79,0x75,0x68,0x53,0x3c,0x31,0x30,0x35,0x3a,0x40,0x42,0x40,0x40, + 0x43,0x4f,0x5c,0x6c,0x75,0x78,0x76,0x7f,0x94,0x92,0x90,0x8b,0x7d,0x7b,0x77,0x6f, + 0x69,0x5e,0x5a,0x58,0x5e,0x63,0x67,0x6c,0x6e,0x6d,0x6a,0x6b,0x6a,0x6b,0x6c,0x6c, + 0x6d,0x6d,0x6b,0x6b,0x69,0x69,0x74,0x7c,0x7b,0x73,0x6d,0x5b,0x5b,0x60,0x66,0x72, + 0x80,0x89,0x90,0xa2,0xb2,0xaf,0xa6,0x92,0x70,0x45,0x3d,0x3f,0x3f,0x41,0x43,0x49, + 0x48,0x4a,0x48,0x47,0x48,0x4b,0x4b,0x4a,0x49,0x49,0x45,0x45,0x44,0x49,0x4a,0x4a, + 0x4d,0x4a,0x40,0x36,0x30,0x2a,0x27,0x24,0x23,0x24,0x28,0x2d,0x32,0x34,0x30,0x33, + 0x37,0x3b,0x41,0x47,0x48,0x49,0x47,0x44,0x41,0x3f,0x3e,0x3f,0x41,0x41,0x46,0x4a, + 0x85,0x8b,0x82,0x7b,0x73,0x65,0x4d,0x38,0x30,0x30,0x33,0x37,0x3d,0x41,0x3f,0x40, + 0x3f,0x45,0x52,0x62,0x6e,0x72,0x75,0x79,0x88,0x8a,0x8a,0x83,0x7d,0x7c,0x77,0x73, + 0x6f,0x63,0x5e,0x5b,0x5b,0x5f,0x6a,0x7d,0x86,0x80,0x73,0x70,0x6f,0x6f,0x6e,0x6e, + 0x70,0x70,0x6d,0x6d,0x6e,0x6d,0x78,0x83,0x7c,0x76,0x72,0x64,0x5e,0x62,0x6a,0x76, + 0x83,0x8f,0x95,0x97,0xac,0xb4,0xae,0x9e,0x86,0x5d,0x45,0x46,0x47,0x47,0x48,0x4d, + 0x4b,0x4b,0x4b,0x4b,0x4d,0x4c,0x4c,0x4a,0x48,0x48,0x46,0x44,0x43,0x45,0x48,0x4a, + 0x48,0x44,0x3b,0x2f,0x26,0x20,0x1d,0x1b,0x1f,0x27,0x2f,0x34,0x38,0x38,0x38,0x35, + 0x37,0x3a,0x3f,0x43,0x46,0x45,0x47,0x4b,0x4c,0x4b,0x4a,0x48,0x48,0x48,0x4b,0x4d, + 0x89,0x8b,0x87,0x7f,0x77,0x66,0x50,0x39,0x30,0x2f,0x33,0x37,0x39,0x3e,0x40,0x3b, + 0x3f,0x44,0x4a,0x52,0x5f,0x68,0x71,0x75,0x7e,0x8d,0x94,0x8f,0x82,0x7d,0x7b,0x7a, + 0x75,0x6c,0x65,0x5d,0x58,0x62,0x86,0x9e,0xa6,0xa8,0x98,0x7b,0x72,0x70,0x6f,0x6f, + 0x6f,0x70,0x6f,0x6e,0x6f,0x70,0x71,0x84,0x80,0x7a,0x76,0x6d,0x67,0x67,0x6f,0x7c, + 0x89,0x94,0x97,0x96,0xa1,0xb4,0xaf,0xa3,0x8e,0x71,0x4a,0x47,0x4b,0x4b,0x4d,0x51, + 0x51,0x4f,0x4c,0x4d,0x4e,0x4e,0x4d,0x4d,0x49,0x47,0x48,0x49,0x48,0x48,0x48,0x4c, + 0x4c,0x4a,0x41,0x36,0x2f,0x26,0x23,0x26,0x2d,0x32,0x35,0x38,0x37,0x35,0x35,0x34, + 0x36,0x39,0x3a,0x3a,0x3a,0x3b,0x43,0x4c,0x4f,0x4f,0x4b,0x45,0x45,0x49,0x4c,0x4d, + 0x82,0x83,0x86,0x83,0x7b,0x6d,0x53,0x3d,0x31,0x30,0x2f,0x33,0x36,0x3c,0x3d,0x3e, + 0x3e,0x3f,0x43,0x43,0x4c,0x5c,0x66,0x6e,0x8f,0xa9,0xa5,0xa2,0x8a,0x7f,0x80,0x80, + 0x7d,0x75,0x68,0x5f,0x59,0x6d,0x92,0x9e,0xa1,0xa2,0x93,0x7d,0x6e,0x6a,0x6a,0x6a, + 0x68,0x69,0x68,0x68,0x69,0x6d,0x6d,0x71,0x76,0x78,0x76,0x70,0x6e,0x6f,0x73,0x81, + 0x8d,0x95,0x9a,0x9a,0x9a,0xa7,0xb3,0xab,0x9e,0x8c,0x6b,0x5d,0x55,0x4f,0x4f,0x53, + 0x52,0x4e,0x4e,0x4e,0x4f,0x4e,0x4e,0x4b,0x4b,0x48,0x4a,0x4c,0x49,0x47,0x48,0x47, + 0x47,0x47,0x42,0x3b,0x39,0x37,0x36,0x38,0x3e,0x3e,0x3b,0x37,0x37,0x34,0x31,0x30, + 0x34,0x35,0x33,0x31,0x2e,0x36,0x3f,0x47,0x46,0x43,0x3d,0x38,0x37,0x38,0x3f,0x43, + 0x74,0x7b,0x83,0x85,0x80,0x72,0x59,0x3e,0x32,0x32,0x31,0x33,0x35,0x39,0x3d,0x3d, + 0x3c,0x3e,0x3d,0x3f,0x41,0x49,0x56,0x67,0x9b,0xa8,0xab,0xa8,0x93,0x86,0x85,0x84, + 0x83,0x76,0x69,0x60,0x59,0x62,0x85,0x9a,0x99,0x8d,0x7c,0x6b,0x63,0x60,0x5f,0x5e, + 0x5e,0x5d,0x5f,0x60,0x62,0x64,0x65,0x69,0x6d,0x75,0x78,0x77,0x77,0x78,0x7c,0x87, + 0x91,0x97,0x9c,0x9d,0x9a,0xa1,0xb3,0xb4,0xb2,0xb7,0xb1,0x97,0x7a,0x5f,0x54,0x52, + 0x50,0x4f,0x4d,0x4e,0x4f,0x4e,0x4e,0x4b,0x4b,0x48,0x4a,0x4b,0x48,0x44,0x46,0x44, + 0x42,0x45,0x42,0x3e,0x3e,0x3c,0x3b,0x3c,0x41,0x3f,0x3e,0x3e,0x36,0x31,0x2c,0x2b, + 0x2b,0x2b,0x29,0x27,0x2b,0x35,0x3d,0x45,0x44,0x3c,0x32,0x2e,0x2c,0x34,0x3a,0x3b, + 0x68,0x78,0x82,0x83,0x7c,0x71,0x5b,0x3d,0x2e,0x31,0x31,0x30,0x32,0x36,0x3c,0x3f, + 0x39,0x3b,0x3a,0x3c,0x3f,0x3f,0x42,0x54,0x8f,0x9b,0xa2,0x9f,0x98,0x93,0x8e,0x87, + 0x7f,0x71,0x67,0x5e,0x56,0x54,0x57,0x62,0x66,0x63,0x5c,0x55,0x55,0x54,0x55,0x54, + 0x54,0x54,0x57,0x59,0x5b,0x5e,0x5f,0x63,0x69,0x73,0x78,0x7c,0x7e,0x80,0x84,0x8b, + 0x95,0x98,0x9d,0x9b,0x9d,0x9d,0xb2,0xb8,0xc2,0xd1,0xcb,0xb7,0x9f,0x75,0x53,0x4f, + 0x4e,0x4d,0x4c,0x4c,0x4e,0x4e,0x4a,0x49,0x4b,0x48,0x49,0x4b,0x47,0x43,0x45,0x45, + 0x46,0x45,0x46,0x45,0x41,0x3f,0x3e,0x3e,0x3c,0x3b,0x39,0x38,0x33,0x2f,0x2c,0x2e, + 0x2d,0x2b,0x2c,0x2e,0x34,0x3b,0x44,0x4d,0x47,0x3e,0x35,0x2d,0x2c,0x33,0x39,0x38, + 0x6a,0x77,0x80,0x82,0x7d,0x74,0x60,0x43,0x31,0x30,0x33,0x30,0x30,0x34,0x39,0x3c, + 0x39,0x39,0x39,0x3a,0x3d,0x3e,0x3b,0x3a,0x5a,0x74,0x82,0x9c,0xa2,0x9e,0x97,0x88, + 0x79,0x6d,0x63,0x5c,0x57,0x53,0x51,0x4f,0x4d,0x4d,0x4d,0x4d,0x4d,0x4e,0x4f,0x50, + 0x51,0x54,0x55,0x59,0x5b,0x5f,0x61,0x64,0x68,0x71,0x7b,0x80,0x83,0x85,0x8a,0x8f, + 0x96,0x9b,0x9e,0x9d,0x9c,0x9d,0xa8,0xb4,0xce,0xdb,0xdb,0xcd,0xb4,0x7d,0x4e,0x4a, + 0x49,0x4a,0x4a,0x4b,0x50,0x4f,0x4e,0x4d,0x4e,0x4a,0x48,0x46,0x42,0x3e,0x3f,0x40, + 0x42,0x41,0x41,0x3f,0x3f,0x3d,0x3d,0x3d,0x3f,0x3c,0x3b,0x3c,0x3f,0x3a,0x38,0x35, + 0x36,0x35,0x36,0x38,0x3f,0x44,0x4e,0x56,0x55,0x49,0x40,0x38,0x35,0x3b,0x40,0x3f, + 0x72,0x7e,0x84,0x83,0x7c,0x73,0x62,0x46,0x31,0x2f,0x35,0x32,0x2f,0x31,0x34,0x3b, + 0x3b,0x38,0x38,0x38,0x3c,0x3c,0x3b,0x34,0x34,0x48,0x6e,0x8f,0x96,0x9c,0x95,0x7e, + 0x72,0x67,0x61,0x5a,0x57,0x54,0x52,0x50,0x4f,0x4d,0x4d,0x4a,0x4b,0x4e,0x50,0x53, + 0x55,0x59,0x5c,0x5f,0x63,0x66,0x67,0x6a,0x6f,0x73,0x7d,0x84,0x86,0x89,0x8e,0x93, + 0x99,0x9d,0x9e,0x9d,0x9f,0x9f,0x9b,0xa8,0xcc,0xde,0xde,0xd3,0xb5,0x79,0x47,0x43, + 0x43,0x45,0x4a,0x4d,0x52,0x52,0x53,0x50,0x52,0x4f,0x4b,0x46,0x42,0x3e,0x3e,0x3f, + 0x3e,0x3f,0x3f,0x3f,0x3d,0x3b,0x39,0x35,0x34,0x38,0x3d,0x42,0x47,0x45,0x42,0x3f, + 0x3e,0x3e,0x3f,0x42,0x45,0x49,0x52,0x59,0x58,0x4d,0x44,0x38,0x34,0x3b,0x40,0x40, + 0x7a,0x85,0x8a,0x86,0x7e,0x76,0x63,0x47,0x30,0x2e,0x33,0x34,0x2e,0x2f,0x31,0x36, + 0x3a,0x38,0x37,0x37,0x38,0x3a,0x3a,0x38,0x32,0x34,0x58,0x70,0x84,0x8c,0x7e,0x75, + 0x6f,0x65,0x61,0x5a,0x57,0x54,0x53,0x52,0x51,0x4f,0x4f,0x4f,0x51,0x53,0x57,0x59, + 0x5d,0x61,0x63,0x69,0x6e,0x72,0x75,0x78,0x7a,0x7d,0x80,0x88,0x8b,0x8d,0x92,0x97, + 0x9a,0x9d,0x9f,0x9f,0xa0,0x9e,0x99,0xa5,0xcc,0xdd,0xdc,0xd4,0xb3,0x74,0x43,0x40, + 0x41,0x44,0x49,0x4e,0x51,0x54,0x56,0x54,0x53,0x50,0x4b,0x46,0x40,0x3c,0x3c,0x3c, + 0x3b,0x39,0x39,0x36,0x35,0x35,0x33,0x2f,0x30,0x36,0x3a,0x3d,0x42,0x43,0x45,0x45, + 0x42,0x42,0x41,0x41,0x45,0x4b,0x4e,0x51,0x50,0x48,0x41,0x37,0x33,0x35,0x3b,0x3c, + 0x81,0x8d,0x8e,0x87,0x7e,0x71,0x5f,0x45,0x30,0x2d,0x33,0x34,0x31,0x2c,0x2f,0x31, + 0x36,0x38,0x35,0x36,0x36,0x37,0x3a,0x39,0x39,0x34,0x36,0x4b,0x5e,0x61,0x69,0x6f, + 0x6a,0x64,0x60,0x5b,0x57,0x55,0x56,0x56,0x54,0x53,0x53,0x57,0x58,0x5a,0x5d,0x5f, + 0x64,0x69,0x6c,0x75,0x7a,0x7f,0x83,0x86,0x87,0x88,0x8a,0x8d,0x8e,0x93,0x95,0x98, + 0x9b,0x9e,0x9f,0x9f,0x9f,0x9e,0x9a,0xa7,0xcd,0xde,0xdd,0xd2,0xae,0x6a,0x3e,0x3c, + 0x3e,0x42,0x47,0x4b,0x50,0x54,0x56,0x55,0x53,0x51,0x4b,0x45,0x3e,0x3a,0x38,0x39, + 0x3b,0x37,0x34,0x2f,0x2b,0x2a,0x2c,0x2c,0x2e,0x32,0x34,0x36,0x3a,0x3e,0x3e,0x40, + 0x41,0x41,0x3f,0x3c,0x3f,0x45,0x46,0x47,0x45,0x40,0x38,0x35,0x2d,0x2d,0x37,0x3c, + 0x83,0x8f,0x8f,0x87,0x7e,0x76,0x63,0x49,0x35,0x2f,0x33,0x33,0x32,0x2d,0x2e,0x30, + 0x34,0x36,0x34,0x35,0x36,0x37,0x38,0x38,0x38,0x37,0x32,0x33,0x3e,0x4b,0x56,0x61, + 0x67,0x63,0x5e,0x5b,0x57,0x56,0x57,0x57,0x57,0x57,0x5a,0x5b,0x5e,0x61,0x66,0x6b, + 0x72,0x77,0x7b,0x81,0x85,0x89,0x8c,0x8d,0x8f,0x90,0x90,0x93,0x92,0x95,0x98,0x9a, + 0x9d,0x9e,0x9f,0x9f,0x9f,0x9c,0x9a,0xab,0xcf,0xdd,0xda,0xcd,0xa3,0x5d,0x3c,0x3b, + 0x3f,0x43,0x45,0x49,0x50,0x54,0x56,0x53,0x50,0x4e,0x46,0x41,0x3e,0x40,0x40,0x43, + 0x46,0x44,0x3d,0x3a,0x36,0x2d,0x27,0x27,0x2a,0x2b,0x2a,0x2b,0x2e,0x30,0x31,0x36, + 0x37,0x39,0x3a,0x37,0x34,0x38,0x3a,0x3c,0x3a,0x38,0x31,0x2c,0x26,0x2a,0x32,0x37, + 0x83,0x8c,0x8d,0x87,0x80,0x76,0x65,0x4a,0x36,0x2d,0x30,0x32,0x34,0x30,0x2d,0x30, + 0x30,0x36,0x36,0x33,0x35,0x35,0x35,0x37,0x37,0x37,0x37,0x34,0x33,0x39,0x45,0x51, + 0x58,0x60,0x60,0x5b,0x56,0x56,0x57,0x58,0x5a,0x5c,0x5e,0x61,0x69,0x6d,0x73,0x79, + 0x7d,0x83,0x86,0x8b,0x8e,0x91,0x91,0x92,0x94,0x95,0x95,0x98,0x99,0x99,0x9a,0x9c, + 0x9d,0x9d,0x9d,0x9d,0x9d,0x9c,0x96,0xac,0xce,0xd9,0xd5,0xc3,0x96,0x50,0x38,0x3b, + 0x41,0x45,0x46,0x49,0x4d,0x53,0x54,0x54,0x4f,0x48,0x3d,0x38,0x38,0x3f,0x45,0x4b, + 0x4d,0x4f,0x4b,0x45,0x3f,0x39,0x33,0x30,0x2e,0x2d,0x29,0x24,0x21,0x24,0x25,0x2f, + 0x32,0x36,0x35,0x36,0x38,0x37,0x32,0x31,0x33,0x31,0x2b,0x2a,0x24,0x27,0x30,0x34, + 0x80,0x88,0x89,0x84,0x7c,0x73,0x61,0x49,0x36,0x29,0x2c,0x30,0x33,0x32,0x2e,0x2f, + 0x31,0x34,0x35,0x33,0x34,0x33,0x32,0x34,0x35,0x35,0x35,0x33,0x33,0x2f,0x38,0x42, + 0x49,0x50,0x54,0x59,0x58,0x57,0x57,0x5a,0x5f,0x64,0x68,0x6e,0x75,0x7b,0x80,0x85, + 0x89,0x8c,0x8e,0x8f,0x93,0x91,0x92,0x93,0x97,0x97,0x98,0x9a,0x9b,0x9b,0x9b,0x9c, + 0x9c,0x9d,0x9d,0x9d,0x9d,0x9d,0x95,0xab,0xcd,0xd1,0xcb,0xb5,0x84,0x43,0x3b,0x3e, + 0x44,0x46,0x49,0x4c,0x4e,0x52,0x54,0x55,0x4f,0x45,0x3c,0x3a,0x39,0x3b,0x48,0x4f, + 0x54,0x57,0x56,0x50,0x46,0x41,0x3d,0x37,0x32,0x30,0x2b,0x26,0x23,0x22,0x25,0x2b, + 0x2f,0x37,0x39,0x39,0x3b,0x3a,0x35,0x30,0x32,0x2e,0x29,0x27,0x27,0x24,0x29,0x2f, + 0x7c,0x84,0x84,0x80,0x79,0x71,0x5d,0x48,0x36,0x28,0x28,0x2f,0x32,0x32,0x2d,0x2f, + 0x31,0x31,0x35,0x34,0x33,0x32,0x30,0x32,0x31,0x32,0x31,0x31,0x31,0x32,0x2f,0x33, + 0x39,0x42,0x47,0x4c,0x54,0x59,0x5e,0x62,0x6a,0x71,0x76,0x7c,0x82,0x87,0x89,0x8d, + 0x8f,0x91,0x91,0x91,0x92,0x93,0x94,0x94,0x98,0x99,0x9a,0x9c,0x9e,0x9e,0x9e,0x9e, + 0x9e,0x9d,0x9c,0x9c,0x9c,0x9c,0x96,0xb0,0xcc,0xc8,0xbc,0xa4,0x6d,0x3f,0x3f,0x44, + 0x48,0x4a,0x4c,0x4e,0x50,0x54,0x59,0x53,0x4d,0x44,0x3c,0x3a,0x3b,0x3e,0x49,0x51, + 0x55,0x58,0x59,0x55,0x4b,0x43,0x3f,0x3a,0x36,0x31,0x2a,0x25,0x24,0x24,0x27,0x2a, + 0x32,0x3b,0x3f,0x40,0x3b,0x3c,0x38,0x33,0x31,0x2b,0x28,0x26,0x25,0x23,0x23,0x2a, + 0x7a,0x83,0x86,0x81,0x7b,0x71,0x60,0x4a,0x35,0x28,0x25,0x2e,0x33,0x34,0x30,0x34, + 0x36,0x35,0x36,0x37,0x35,0x34,0x33,0x32,0x33,0x31,0x30,0x2f,0x2f,0x2f,0x30,0x31, + 0x30,0x31,0x38,0x41,0x4a,0x58,0x66,0x71,0x79,0x7f,0x83,0x87,0x89,0x8c,0x90,0x91, + 0x8f,0x8f,0x90,0x92,0x91,0x91,0x94,0x95,0x98,0x9b,0x9d,0x9e,0xa0,0xa0,0xa0,0xa1, + 0xa0,0x9f,0x9f,0x9f,0x9f,0x9a,0x9c,0xb9,0xbf,0xb8,0xa9,0x91,0x57,0x40,0x45,0x4b, + 0x4d,0x4e,0x51,0x52,0x52,0x52,0x52,0x52,0x4b,0x45,0x3f,0x3d,0x3b,0x41,0x49,0x51, + 0x55,0x57,0x55,0x52,0x4d,0x47,0x41,0x3a,0x38,0x32,0x2c,0x25,0x27,0x29,0x2e,0x34, + 0x3d,0x45,0x44,0x44,0x41,0x3f,0x3a,0x35,0x2e,0x2a,0x25,0x21,0x1f,0x21,0x23,0x28, + 0x78,0x82,0x89,0x8a,0x83,0x78,0x75,0x81,0x84,0x7b,0x76,0x77,0x6c,0x5a,0x50,0x51, + 0x53,0x54,0x52,0x52,0x52,0x52,0x52,0x52,0x4f,0x4d,0x49,0x46,0x43,0x40,0x3c,0x3a, + 0x37,0x35,0x34,0x38,0x41,0x52,0x64,0x77,0x85,0x8a,0x8c,0x8c,0x90,0x91,0x8f,0x93, + 0x97,0x9d,0xa3,0xa4,0x9f,0x99,0x96,0x96,0x9b,0x9d,0xa0,0xa0,0xa1,0xa2,0xa3,0xa3, + 0xa2,0xa0,0xa0,0x9f,0x9e,0x9b,0xa5,0xaf,0xaa,0xa3,0x96,0x71,0x4b,0x4b,0x4d,0x50, + 0x53,0x55,0x57,0x55,0x53,0x52,0x4e,0x4f,0x4b,0x48,0x44,0x3f,0x3e,0x43,0x47,0x4d, + 0x50,0x53,0x53,0x52,0x4d,0x4b,0x44,0x3e,0x35,0x31,0x2d,0x29,0x28,0x2c,0x35,0x3f, + 0x46,0x4b,0x49,0x44,0x43,0x3f,0x3a,0x37,0x33,0x2c,0x26,0x1f,0x1d,0x23,0x28,0x32, + 0x7a,0x84,0x89,0x8d,0x89,0x88,0xa2,0xb7,0xc4,0xbf,0xb2,0xa3,0x93,0x83,0x79,0x78, + 0x7a,0x7d,0x7c,0x7c,0x7e,0x7e,0x7f,0x7f,0x7d,0x7b,0x7c,0x79,0x76,0x73,0x71,0x6d, + 0x67,0x62,0x58,0x4d,0x4c,0x53,0x5d,0x70,0x81,0x8c,0x8f,0x8d,0x8f,0x90,0x9d,0xad, + 0xb8,0xbe,0xc2,0xc0,0xb3,0xa5,0x9c,0x99,0x9c,0x9d,0xa0,0x9f,0xa1,0xa5,0xa6,0xa5, + 0xa3,0xa1,0x9f,0x9e,0x9b,0x9c,0x9d,0x9b,0x92,0x89,0x77,0x50,0x4a,0x4d,0x51,0x54, + 0x58,0x5a,0x5b,0x5a,0x56,0x54,0x51,0x4d,0x4b,0x48,0x46,0x41,0x3f,0x43,0x45,0x46, + 0x47,0x4a,0x4c,0x4c,0x49,0x48,0x44,0x40,0x39,0x31,0x31,0x2e,0x2b,0x2e,0x39,0x44, + 0x48,0x4a,0x47,0x42,0x3f,0x39,0x3a,0x3a,0x39,0x33,0x2c,0x23,0x21,0x2a,0x2f,0x37, + 0x7c,0x83,0x88,0x8e,0x8a,0x90,0xad,0xc1,0xc8,0xbb,0xae,0xa1,0x93,0x89,0x87,0x8a, + 0x8c,0x8f,0x8f,0x92,0x94,0x94,0x96,0x96,0x96,0x94,0x94,0x95,0x96,0x93,0x90,0x90, + 0x8f,0x89,0x84,0x7d,0x76,0x6e,0x6e,0x72,0x7a,0x83,0x89,0x8f,0x92,0xa0,0xb5,0xca, + 0xd7,0xde,0xde,0xd4,0xc6,0xb1,0x9d,0x99,0x9b,0x9d,0x9e,0x9e,0xa4,0xa8,0xa9,0xa8, + 0xa6,0xa1,0x9d,0x9c,0x9a,0x97,0x8f,0x84,0x77,0x68,0x55,0x4c,0x4f,0x52,0x55,0x5a, + 0x5e,0x60,0x5f,0x5d,0x5a,0x57,0x55,0x52,0x4f,0x4d,0x49,0x46,0x45,0x46,0x46,0x44, + 0x42,0x40,0x43,0x45,0x45,0x42,0x42,0x41,0x3e,0x39,0x38,0x35,0x30,0x2e,0x35,0x3c, + 0x41,0x43,0x41,0x3e,0x36,0x34,0x36,0x3b,0x3d,0x3b,0x36,0x31,0x32,0x37,0x3a,0x3f, + 0x7d,0x81,0x86,0x8d,0x88,0x77,0x75,0x7c,0x7b,0x6f,0x70,0x74,0x7a,0x81,0x83,0x89, + 0x8b,0x8e,0x8f,0x90,0x92,0x94,0x96,0x96,0x97,0x94,0x95,0x98,0x9a,0x9c,0x9e,0x9f, + 0x9d,0x9e,0x9a,0x94,0x8f,0x84,0x7a,0x79,0x7d,0x82,0x88,0x8d,0x8f,0xaf,0xcd,0xdf, + 0xe9,0xed,0xea,0xe2,0xd1,0xb8,0x9c,0x9a,0x9a,0x9d,0x9e,0x9f,0xa9,0xaa,0xab,0xaa, + 0xa6,0xa2,0x9e,0x9a,0x98,0x91,0x81,0x6f,0x5d,0x53,0x4b,0x4e,0x50,0x54,0x58,0x60, + 0x63,0x63,0x63,0x61,0x5e,0x5b,0x57,0x55,0x52,0x50,0x4c,0x4a,0x4a,0x49,0x45,0x45, + 0x42,0x39,0x39,0x3b,0x3d,0x42,0x3e,0x3e,0x40,0x3d,0x3b,0x3b,0x36,0x2f,0x34,0x33, + 0x37,0x3a,0x3a,0x3a,0x34,0x31,0x36,0x3b,0x3e,0x42,0x40,0x3e,0x3f,0x41,0x42,0x46, + 0x7f,0x80,0x83,0x88,0x83,0x73,0x6b,0x68,0x6e,0x74,0x78,0x78,0x7d,0x7c,0x81,0x86, + 0x89,0x8b,0x8c,0x8e,0x8f,0x90,0x90,0x91,0x91,0x90,0x91,0x91,0x93,0x93,0x96,0x96, + 0x97,0x97,0x98,0x99,0x99,0x91,0x8c,0x81,0x7d,0x80,0x85,0x86,0x8e,0xbe,0xdc,0xe9, + 0xf0,0xf2,0xee,0xe5,0xd4,0xb8,0x9e,0x9a,0x9d,0x9e,0xa1,0xa4,0xac,0xac,0xad,0xab, + 0xa6,0xa1,0x9e,0x9a,0x95,0x89,0x79,0x66,0x56,0x4e,0x4c,0x4b,0x4f,0x54,0x5a,0x5c, + 0x61,0x63,0x62,0x61,0x5f,0x5c,0x5a,0x57,0x54,0x52,0x4f,0x4d,0x4c,0x4c,0x4a,0x45, + 0x42,0x3d,0x35,0x33,0x35,0x3b,0x3d,0x3c,0x40,0x3f,0x41,0x42,0x3b,0x34,0x34,0x34, + 0x33,0x34,0x37,0x36,0x32,0x33,0x36,0x3a,0x3c,0x46,0x4c,0x4a,0x4b,0x49,0x46,0x48, + 0x7f,0x7e,0x7f,0x7f,0x7f,0x76,0x6b,0x6d,0x73,0x7b,0x7c,0x7c,0x7b,0x7d,0x83,0x87, + 0x8a,0x8c,0x8d,0x8d,0x8e,0x91,0x90,0x90,0x90,0x91,0x91,0x92,0x92,0x93,0x96,0x95, + 0x94,0x95,0x95,0x94,0x91,0x8d,0x88,0x83,0x7f,0x7d,0x7d,0x7e,0x90,0xc1,0xdc,0xeb, + 0xf1,0xf3,0xf0,0xe8,0xd5,0xb7,0x9e,0x9b,0x9f,0xa1,0xa3,0xa6,0xad,0xad,0xaf,0xac, + 0xa6,0xa0,0x9b,0x98,0x91,0x80,0x72,0x5f,0x50,0x4d,0x49,0x48,0x4d,0x53,0x57,0x5d, + 0x60,0x63,0x61,0x5e,0x5c,0x59,0x56,0x55,0x52,0x52,0x4e,0x4b,0x4b,0x4b,0x4b,0x47, + 0x42,0x3b,0x38,0x34,0x30,0x34,0x37,0x3a,0x41,0x44,0x48,0x4a,0x44,0x3c,0x38,0x35, + 0x32,0x33,0x34,0x34,0x35,0x37,0x35,0x38,0x3d,0x47,0x4e,0x51,0x51,0x4e,0x48,0x45, + 0x7d,0x7b,0x7b,0x7e,0x7d,0x77,0x6c,0x72,0x75,0x7e,0x7d,0x7b,0x7b,0x80,0x84,0x87, + 0x8a,0x8d,0x8f,0x8e,0x91,0x94,0x91,0x90,0x90,0x90,0x90,0x91,0x93,0x94,0x95,0x93, + 0x94,0x95,0x96,0x96,0x8f,0x8a,0x86,0x81,0x81,0x78,0x78,0x78,0x89,0xb4,0xd5,0xea, + 0xf1,0xf3,0xf0,0xe7,0xd5,0xb8,0xa0,0x9c,0xa3,0xa5,0xa8,0xac,0xb0,0xb0,0xb0,0xab, + 0xa6,0x9f,0x9c,0x92,0x83,0x75,0x68,0x56,0x4c,0x4a,0x49,0x48,0x4d,0x50,0x55,0x59, + 0x5a,0x5e,0x5b,0x59,0x54,0x52,0x50,0x52,0x51,0x4e,0x4c,0x4b,0x4a,0x48,0x4b,0x4d, + 0x49,0x41,0x3a,0x34,0x2f,0x30,0x31,0x3a,0x47,0x4c,0x4c,0x4d,0x48,0x44,0x41,0x38, + 0x32,0x32,0x35,0x35,0x36,0x39,0x3b,0x3d,0x43,0x4a,0x4f,0x54,0x53,0x4e,0x48,0x43, + 0x7b,0x79,0x7c,0x82,0x80,0x7a,0x70,0x74,0x77,0x7c,0x7b,0x7a,0x7c,0x7f,0x84,0x87, + 0x8a,0x8d,0x90,0x91,0x94,0x93,0x91,0x91,0x8e,0x8f,0x91,0x8f,0x94,0x94,0x93,0x92, + 0x95,0x95,0x97,0x92,0x8a,0x88,0x85,0x84,0x81,0x7b,0x77,0x73,0x77,0x93,0xc1,0xde, + 0xef,0xf3,0xf1,0xe8,0xd6,0xb9,0x9f,0x9f,0xa3,0xa6,0xaa,0xb1,0xb2,0xb2,0xb1,0xad, + 0xa3,0xa0,0x94,0x87,0x76,0x66,0x5a,0x52,0x4e,0x4a,0x49,0x49,0x4a,0x4d,0x51,0x53, + 0x53,0x54,0x52,0x52,0x4b,0x4d,0x50,0x50,0x4f,0x4d,0x4b,0x48,0x46,0x47,0x4b,0x4e, + 0x4d,0x47,0x41,0x34,0x2c,0x2d,0x33,0x38,0x42,0x49,0x4e,0x4e,0x49,0x47,0x45,0x3d, + 0x39,0x36,0x38,0x36,0x37,0x3a,0x3f,0x40,0x46,0x4b,0x4d,0x54,0x56,0x50,0x4b,0x45, + 0x7e,0x81,0x83,0x87,0x83,0x78,0x71,0x73,0x7a,0x7c,0x7e,0x7f,0x81,0x84,0x84,0x87, + 0x8a,0x8d,0x91,0x90,0x94,0x95,0x92,0x91,0x8f,0x92,0x90,0x90,0x95,0x92,0x92,0x91, + 0x92,0x96,0x94,0x8c,0x87,0x87,0x87,0x89,0x82,0x7d,0x78,0x72,0x72,0x75,0x99,0xce, + 0xe6,0xef,0xf1,0xe7,0xd5,0xb8,0x9f,0xa1,0xa5,0xa9,0xac,0xb3,0xb4,0xb3,0xb1,0xaa, + 0xa2,0x9c,0x8a,0x76,0x67,0x5a,0x4d,0x50,0x4f,0x4f,0x4e,0x4e,0x4f,0x52,0x54,0x52, + 0x52,0x4f,0x4f,0x4e,0x4b,0x4c,0x4e,0x50,0x4e,0x4e,0x4c,0x47,0x47,0x47,0x4b,0x4e, + 0x4f,0x4c,0x45,0x38,0x2c,0x2a,0x33,0x3c,0x3e,0x48,0x4f,0x51,0x4c,0x4a,0x45,0x40, + 0x3e,0x3b,0x3b,0x39,0x3c,0x41,0x43,0x44,0x43,0x4a,0x4f,0x55,0x54,0x4f,0x49,0x45, + 0x7f,0x83,0x87,0x85,0x7f,0x72,0x70,0x71,0x78,0x7c,0x83,0x86,0x8a,0x8e,0x8c,0x8c, + 0x89,0x8d,0x90,0x92,0x94,0x95,0x94,0x92,0x92,0x90,0x90,0x91,0x92,0x8f,0x90,0x8f, + 0x94,0x94,0x8e,0x89,0x86,0x86,0x8a,0x89,0x86,0x7d,0x78,0x72,0x70,0x72,0x7d,0xb5, + 0xdb,0xea,0xea,0xe1,0xcd,0xb2,0xa1,0xa3,0xa7,0xa9,0xb0,0xb5,0xb4,0xb5,0xaf,0xa6, + 0xa0,0x93,0x7c,0x65,0x5a,0x4d,0x4c,0x4d,0x4f,0x51,0x53,0x54,0x54,0x53,0x52,0x53, + 0x53,0x4e,0x4e,0x4d,0x4c,0x4e,0x4e,0x51,0x50,0x50,0x4e,0x4a,0x46,0x47,0x48,0x4a, + 0x49,0x46,0x42,0x39,0x30,0x2d,0x32,0x3d,0x44,0x4e,0x52,0x50,0x4c,0x4b,0x48,0x44, + 0x45,0x43,0x3f,0x3f,0x45,0x42,0x45,0x43,0x41,0x48,0x4d,0x50,0x53,0x50,0x4a,0x43, + 0x7f,0x81,0x81,0x80,0x79,0x6d,0x6d,0x71,0x79,0x82,0x8b,0x8c,0x8f,0x90,0x94,0x96, + 0x92,0x92,0x90,0x92,0x95,0x96,0x95,0x94,0x90,0x8f,0x92,0x93,0x90,0x8f,0x8e,0x91, + 0x94,0x92,0x8d,0x87,0x86,0x89,0x8d,0x8b,0x87,0x80,0x7b,0x76,0x71,0x6f,0x74,0x98, + 0xc7,0xdc,0xde,0xd3,0xc0,0xab,0xa3,0xa5,0xa8,0xaa,0xaf,0xb4,0xb5,0xb3,0xab,0xa1, + 0x95,0x83,0x6f,0x5e,0x4e,0x4c,0x4e,0x4e,0x51,0x53,0x54,0x57,0x59,0x58,0x55,0x56, + 0x55,0x52,0x52,0x52,0x53,0x53,0x54,0x56,0x55,0x55,0x51,0x4e,0x4c,0x49,0x45,0x47, + 0x47,0x45,0x41,0x3c,0x3a,0x39,0x3c,0x43,0x4a,0x50,0x52,0x4f,0x50,0x4d,0x4c,0x4a, + 0x49,0x49,0x48,0x4a,0x4b,0x46,0x43,0x40,0x3f,0x45,0x4d,0x50,0x51,0x51,0x4e,0x46, + 0x7e,0x7d,0x7e,0x7a,0x75,0x6d,0x6b,0x70,0x7b,0x8d,0x8a,0x88,0x8c,0x90,0x96,0x98, + 0x98,0x97,0x93,0x91,0x93,0x94,0x93,0x92,0x91,0x91,0x92,0x91,0x8f,0x8f,0x8f,0x92, + 0x93,0x90,0x89,0x85,0x86,0x8a,0x8f,0x8c,0x88,0x81,0x7b,0x77,0x73,0x72,0x74,0x84, + 0xa9,0xc1,0xc7,0xc0,0xae,0xa2,0xa4,0xa6,0xa9,0xac,0xb0,0xb2,0xaf,0xa9,0xa2,0x90, + 0x81,0x6f,0x61,0x58,0x52,0x51,0x51,0x50,0x50,0x51,0x53,0x56,0x5a,0x5c,0x5c,0x5a, + 0x58,0x56,0x55,0x56,0x57,0x56,0x57,0x57,0x57,0x55,0x52,0x4f,0x4c,0x4a,0x43,0x43, + 0x44,0x44,0x43,0x42,0x43,0x43,0x45,0x49,0x4e,0x50,0x4e,0x4f,0x51,0x4c,0x4a,0x4b, + 0x4b,0x4d,0x4f,0x4d,0x4a,0x46,0x41,0x3d,0x3a,0x40,0x4a,0x4f,0x54,0x52,0x4e,0x45, + 0x7f,0x7f,0x86,0x81,0x7a,0x71,0x6b,0x73,0x89,0x91,0x88,0x7f,0x8f,0xa0,0xa4,0x9a, + 0x97,0x96,0x97,0x97,0x91,0x90,0x92,0x91,0x91,0x92,0x91,0x91,0x8f,0x8d,0x90,0x91, + 0x91,0x8d,0x88,0x86,0x88,0x8b,0x8f,0x8c,0x87,0x81,0x7a,0x77,0x73,0x72,0x73,0x78, + 0x89,0x9d,0xa2,0xa0,0xa1,0xa2,0xa5,0xa6,0xa9,0xac,0xaf,0xac,0xa7,0x9d,0x8c,0x77, + 0x6e,0x63,0x5b,0x57,0x5d,0x5d,0x5a,0x56,0x54,0x54,0x54,0x59,0x5c,0x5d,0x61,0x5f, + 0x5d,0x5b,0x59,0x59,0x5b,0x5b,0x5d,0x5c,0x5b,0x5b,0x57,0x54,0x50,0x4c,0x4a,0x48, + 0x47,0x46,0x48,0x49,0x4a,0x4b,0x4e,0x51,0x52,0x52,0x51,0x4c,0x4d,0x49,0x4a,0x4b, + 0x4b,0x4c,0x51,0x51,0x4c,0x48,0x44,0x3f,0x38,0x3f,0x49,0x50,0x54,0x54,0x50,0x48, + 0x83,0x83,0x8c,0x89,0x81,0x72,0x6d,0x7d,0x8c,0x8e,0x80,0x88,0xa5,0xab,0xaa,0x97, + 0x8a,0x8c,0x91,0x9a,0x9c,0x99,0x94,0x91,0x91,0x92,0x93,0x93,0x90,0x8f,0x90,0x8f, + 0x8e,0x89,0x87,0x85,0x88,0x8c,0x8e,0x8c,0x85,0x81,0x7b,0x78,0x74,0x73,0x74,0x75, + 0x75,0x7f,0x87,0x91,0x9c,0xa2,0xa5,0xa8,0xaa,0xad,0xad,0xa7,0x9c,0x8d,0x75,0x66, + 0x61,0x5a,0x59,0x60,0x65,0x63,0x60,0x5c,0x58,0x55,0x57,0x59,0x5c,0x63,0x65,0x62, + 0x61,0x5f,0x5a,0x59,0x5c,0x5c,0x61,0x62,0x60,0x5f,0x5b,0x57,0x50,0x51,0x4e,0x4b, + 0x4a,0x49,0x4b,0x4c,0x4e,0x4f,0x52,0x51,0x4f,0x4f,0x4e,0x48,0x47,0x47,0x46,0x4c, + 0x4d,0x4e,0x52,0x53,0x4f,0x4a,0x44,0x41,0x3e,0x44,0x49,0x50,0x54,0x54,0x4f,0x49, + 0x82,0x87,0x8c,0x89,0x82,0x77,0x73,0x87,0x8c,0x8a,0x78,0x98,0xb0,0xb3,0xa7,0x82, + 0x6e,0x82,0x8f,0x9e,0xa1,0x9f,0x9b,0x97,0x90,0x94,0x94,0x94,0x94,0x91,0x90,0x8d, + 0x8c,0x87,0x88,0x89,0x8a,0x8b,0x8f,0x8d,0x86,0x80,0x7c,0x77,0x75,0x73,0x75,0x74, + 0x72,0x70,0x7a,0x88,0x96,0xa1,0xa6,0xa7,0xaa,0xac,0xad,0xa1,0x90,0x7b,0x65,0x59, + 0x58,0x55,0x5b,0x64,0x67,0x69,0x66,0x62,0x5c,0x5c,0x5a,0x5b,0x62,0x66,0x69,0x68, + 0x65,0x62,0x5d,0x5a,0x5e,0x63,0x66,0x67,0x62,0x60,0x5d,0x58,0x55,0x55,0x53,0x4e, + 0x4d,0x4d,0x4b,0x4b,0x51,0x54,0x53,0x50,0x4b,0x49,0x4a,0x47,0x44,0x42,0x45,0x4e, + 0x4f,0x51,0x55,0x52,0x53,0x50,0x4d,0x4b,0x47,0x4d,0x50,0x52,0x55,0x55,0x51,0x4b, + 0x82,0x86,0x8d,0x8f,0x86,0x77,0x7a,0x87,0x8a,0x81,0x74,0x95,0xa9,0xb0,0x9a,0x72, + 0x5f,0x75,0x8d,0xa0,0xa0,0xa0,0x9d,0x9b,0x97,0x94,0x95,0x95,0x93,0x93,0x93,0x8f, + 0x8c,0x89,0x8a,0x8a,0x8b,0x8d,0x8f,0x8b,0x86,0x81,0x7d,0x78,0x75,0x73,0x74,0x76, + 0x72,0x6c,0x6f,0x7c,0x8e,0x9f,0xa9,0xa8,0xa7,0xad,0xa8,0x9b,0x86,0x6e,0x5c,0x53, + 0x53,0x59,0x61,0x66,0x68,0x67,0x66,0x63,0x5f,0x5e,0x60,0x61,0x64,0x6a,0x6d,0x6c, + 0x67,0x62,0x61,0x60,0x5f,0x63,0x67,0x68,0x66,0x64,0x5f,0x5a,0x58,0x55,0x52,0x4f, + 0x4e,0x4d,0x49,0x4a,0x4b,0x4e,0x4e,0x4c,0x4a,0x4a,0x46,0x44,0x44,0x41,0x45,0x4d, + 0x51,0x50,0x53,0x55,0x53,0x55,0x53,0x50,0x4f,0x52,0x56,0x55,0x55,0x56,0x52,0x4a, + 0x7b,0x82,0x8a,0x8d,0x81,0x77,0x7e,0x84,0x88,0x79,0x76,0x91,0xa6,0xa9,0x94,0x73, + 0x67,0x7e,0x91,0x97,0x9e,0x9e,0x9b,0x9b,0x9b,0x9a,0x9a,0x98,0x96,0x96,0x97,0x94, + 0x91,0x90,0x8f,0x8d,0x8c,0x8c,0x8f,0x8a,0x85,0x81,0x7e,0x78,0x75,0x73,0x74,0x76, + 0x71,0x69,0x65,0x72,0x88,0x9a,0xa7,0xab,0xae,0xb2,0xad,0x9f,0x8a,0x6d,0x59,0x57, + 0x58,0x5f,0x62,0x63,0x64,0x62,0x64,0x64,0x63,0x63,0x65,0x67,0x69,0x6c,0x6f,0x70, + 0x6c,0x66,0x61,0x60,0x63,0x64,0x66,0x67,0x67,0x66,0x5e,0x59,0x58,0x55,0x4f,0x4f, + 0x4c,0x49,0x46,0x4b,0x49,0x49,0x49,0x47,0x48,0x48,0x42,0x3e,0x41,0x41,0x46,0x4b, + 0x53,0x55,0x56,0x56,0x57,0x54,0x53,0x54,0x59,0x5a,0x5e,0x5c,0x5a,0x5b,0x56,0x4e, + 0x74,0x7f,0x8a,0x86,0x7a,0x71,0x7a,0x81,0x83,0x74,0x79,0x8a,0xa9,0xb0,0x99,0x7d, + 0x6f,0x86,0x94,0x98,0x96,0x96,0x96,0x9a,0x9b,0x9c,0x9d,0x9c,0x9c,0x9b,0x98,0x9a, + 0x99,0x97,0x95,0x91,0x8d,0x8d,0x8c,0x89,0x85,0x81,0x7e,0x78,0x74,0x73,0x75,0x75, + 0x71,0x69,0x66,0x6b,0x7e,0x92,0xa1,0xad,0xba,0xbc,0xb7,0xab,0x96,0x76,0x64,0x62, + 0x5d,0x5e,0x5f,0x60,0x5f,0x5d,0x5d,0x5f,0x62,0x64,0x68,0x6a,0x6e,0x6f,0x70,0x71, + 0x6f,0x68,0x60,0x5f,0x61,0x64,0x67,0x66,0x67,0x63,0x5b,0x57,0x53,0x54,0x4e,0x4c, + 0x47,0x46,0x45,0x49,0x48,0x47,0x46,0x47,0x46,0x3f,0x3e,0x39,0x3b,0x42,0x44,0x4a, + 0x55,0x59,0x59,0x58,0x55,0x55,0x56,0x58,0x5e,0x60,0x63,0x5f,0x5e,0x5a,0x57,0x4c, + 0x74,0x80,0x8b,0x86,0x7a,0x6f,0x78,0x7c,0x7c,0x73,0x7b,0x8a,0xb0,0xb5,0xa7,0x8b, + 0x7b,0x8b,0x93,0x99,0x96,0x94,0x94,0x97,0x9b,0x9d,0x9d,0xa0,0xa2,0xa3,0xa1,0xa1, + 0xa0,0x9e,0x9a,0x93,0x91,0x8f,0x8c,0x88,0x85,0x81,0x7d,0x78,0x74,0x73,0x75,0x75, + 0x72,0x6a,0x65,0x64,0x77,0x8f,0xa8,0xbc,0xcb,0xce,0xc6,0xb9,0xa5,0x86,0x6b,0x63, + 0x60,0x5d,0x5c,0x5c,0x5b,0x58,0x59,0x5b,0x5f,0x62,0x67,0x6a,0x6d,0x6f,0x6e,0x6e, + 0x6d,0x65,0x5e,0x5a,0x59,0x5e,0x62,0x67,0x66,0x61,0x5a,0x53,0x50,0x4f,0x4a,0x49, + 0x46,0x45,0x48,0x4c,0x4c,0x49,0x4a,0x48,0x3e,0x3c,0x38,0x36,0x38,0x40,0x46,0x4d, + 0x55,0x59,0x5b,0x5b,0x5a,0x58,0x57,0x59,0x5c,0x5f,0x5f,0x62,0x5c,0x57,0x52,0x4b, + 0x78,0x88,0x89,0x85,0x74,0x6e,0x75,0x7a,0x79,0x76,0x7a,0x8a,0xac,0xae,0xa2,0x8b, + 0x83,0x8e,0x94,0x97,0x95,0x95,0x94,0x95,0x97,0x99,0x9e,0xa2,0xa3,0xa5,0xa9,0xa8, + 0xa5,0xa1,0x9b,0x98,0x95,0x94,0x8d,0x86,0x83,0x7f,0x7d,0x77,0x74,0x74,0x74,0x74, + 0x72,0x6a,0x65,0x62,0x68,0x8f,0xae,0xc8,0xd8,0xda,0xd3,0xc6,0xac,0x8e,0x6e,0x61, + 0x5c,0x58,0x57,0x56,0x53,0x55,0x57,0x5a,0x5d,0x60,0x61,0x66,0x6a,0x6c,0x6d,0x6f, + 0x6b,0x63,0x5d,0x56,0x56,0x59,0x5f,0x67,0x64,0x5e,0x58,0x52,0x4a,0x45,0x41,0x3f, + 0x3e,0x40,0x47,0x4f,0x4e,0x4d,0x4b,0x44,0x3c,0x39,0x33,0x33,0x36,0x3b,0x41,0x4d, + 0x55,0x58,0x5c,0x5f,0x60,0x59,0x57,0x59,0x5a,0x60,0x64,0x62,0x5f,0x57,0x50,0x4a, + 0x7f,0x8b,0x8a,0x83,0x73,0x70,0x73,0x76,0x75,0x77,0x7a,0x85,0x9d,0xa2,0x9c,0x89, + 0x8d,0x93,0x98,0x99,0x96,0x95,0x95,0x96,0x9a,0x9b,0x9c,0x9f,0xa5,0xa7,0xaa,0xa9, + 0xa9,0xa4,0x9d,0x9e,0x9e,0x9b,0x95,0x8b,0x86,0x81,0x7e,0x78,0x74,0x73,0x75,0x76, + 0x75,0x6d,0x67,0x63,0x66,0x8c,0xb1,0xcf,0xdd,0xe1,0xd9,0xc8,0xaf,0x8d,0x6c,0x5d, + 0x57,0x57,0x56,0x56,0x57,0x57,0x58,0x59,0x5c,0x61,0x62,0x64,0x68,0x6c,0x70,0x72, + 0x70,0x69,0x61,0x57,0x53,0x57,0x60,0x66,0x66,0x5e,0x55,0x4d,0x43,0x40,0x3c,0x3b, + 0x39,0x40,0x49,0x4f,0x51,0x53,0x51,0x47,0x3e,0x38,0x31,0x31,0x37,0x3c,0x42,0x4b, + 0x52,0x57,0x5b,0x62,0x63,0x5e,0x59,0x57,0x57,0x5d,0x61,0x63,0x5d,0x54,0x4b,0x48, + 0x82,0x8e,0x8c,0x84,0x74,0x74,0x72,0x72,0x71,0x77,0x77,0x7e,0x85,0x8f,0x93,0x94, + 0x94,0x98,0x98,0x97,0x96,0x96,0x95,0x97,0x99,0x9b,0x9a,0x9e,0x9f,0xa5,0xa8,0xa8, + 0xa6,0xa4,0xa5,0xa6,0xa3,0xa0,0x98,0x8f,0x8a,0x84,0x7f,0x79,0x76,0x74,0x77,0x78, + 0x77,0x71,0x6d,0x68,0x66,0x84,0xb3,0xd2,0xe1,0xe2,0xd7,0xc5,0xae,0x8e,0x6b,0x5b, + 0x54,0x52,0x54,0x57,0x5a,0x5c,0x5b,0x5b,0x5d,0x60,0x62,0x64,0x6b,0x6e,0x76,0x79, + 0x75,0x6c,0x64,0x5a,0x56,0x59,0x5f,0x65,0x66,0x5d,0x55,0x48,0x3e,0x38,0x35,0x33, + 0x36,0x43,0x4c,0x51,0x54,0x56,0x4f,0x47,0x40,0x38,0x31,0x33,0x3c,0x41,0x43,0x4a, + 0x50,0x53,0x59,0x60,0x62,0x5e,0x5a,0x53,0x53,0x56,0x5c,0x5c,0x58,0x50,0x49,0x45, + 0x89,0x91,0x8b,0x88,0x7e,0x77,0x70,0x6e,0x71,0x78,0x76,0x80,0x87,0x90,0x95,0x99, + 0x99,0x9a,0x98,0x97,0x96,0x95,0x96,0x97,0x9a,0x9c,0x9d,0xa0,0x9e,0xa1,0xa4,0xa7, + 0xa6,0xa5,0xa5,0xa7,0xa6,0xa4,0x95,0x90,0x8c,0x87,0x83,0x79,0x75,0x75,0x77,0x79, + 0x79,0x75,0x71,0x6c,0x6a,0x7d,0xb1,0xce,0xde,0xe0,0xd6,0xc1,0xa8,0x89,0x6f,0x62, + 0x56,0x55,0x56,0x5a,0x5e,0x62,0x65,0x62,0x62,0x62,0x62,0x67,0x6d,0x74,0x79,0x7d, + 0x78,0x71,0x67,0x60,0x5d,0x5f,0x62,0x67,0x66,0x5e,0x55,0x49,0x3c,0x34,0x33,0x31, + 0x36,0x42,0x4a,0x4e,0x51,0x53,0x4d,0x46,0x43,0x40,0x3b,0x3c,0x43,0x46,0x46,0x4d, + 0x4f,0x4e,0x55,0x62,0x65,0x62,0x5e,0x56,0x52,0x57,0x5b,0x5c,0x5b,0x56,0x4f,0x4b, + 0x88,0x8f,0x8d,0x89,0x80,0x77,0x70,0x6b,0x70,0x77,0x77,0x82,0x8a,0x92,0x96,0x99, + 0x99,0x9a,0x97,0x97,0x97,0x96,0x97,0x97,0x99,0x9c,0x9f,0x9f,0x9f,0xa1,0xa0,0xa1, + 0xa0,0xa2,0xa2,0xa5,0xa4,0xa1,0x93,0x91,0x8d,0x8a,0x87,0x7b,0x75,0x75,0x77,0x79, + 0x79,0x77,0x72,0x6e,0x6e,0x7f,0xae,0xcb,0xd9,0xdb,0xcf,0xba,0xa1,0x85,0x6e,0x6a, + 0x63,0x5e,0x5c,0x5e,0x61,0x63,0x68,0x68,0x68,0x66,0x66,0x6a,0x6f,0x76,0x7a,0x7c, + 0x7a,0x73,0x68,0x64,0x60,0x61,0x65,0x69,0x65,0x5d,0x54,0x48,0x3c,0x33,0x31,0x31, + 0x35,0x3d,0x43,0x49,0x4d,0x4f,0x4d,0x4b,0x4b,0x4a,0x47,0x44,0x47,0x48,0x46,0x4b, + 0x4c,0x4d,0x54,0x5f,0x64,0x61,0x5d,0x55,0x50,0x53,0x57,0x58,0x59,0x57,0x50,0x49, + 0x82,0x8a,0x8e,0x8a,0x82,0x76,0x6e,0x6a,0x72,0x75,0x7b,0x83,0x8e,0x95,0x97,0x98, + 0x98,0x98,0x98,0x98,0x96,0x96,0x98,0x99,0x9b,0x9d,0x9f,0x9f,0x9f,0xa1,0xa0,0xa0, + 0x9d,0x9e,0x9f,0xa2,0xa1,0x9d,0x94,0x98,0x9a,0x9a,0x90,0x83,0x78,0x74,0x77,0x7b, + 0x7a,0x79,0x77,0x72,0x6f,0x7e,0xab,0xc6,0xd3,0xd2,0xc2,0xb0,0x98,0x81,0x76,0x72, + 0x70,0x6b,0x64,0x63,0x65,0x66,0x68,0x6c,0x69,0x67,0x68,0x6b,0x70,0x75,0x78,0x78, + 0x74,0x6e,0x65,0x62,0x5f,0x5f,0x63,0x67,0x62,0x59,0x53,0x49,0x3a,0x33,0x31,0x33, + 0x35,0x39,0x42,0x49,0x4c,0x4f,0x52,0x52,0x51,0x52,0x52,0x4a,0x49,0x48,0x45,0x47, + 0x49,0x4e,0x55,0x5f,0x60,0x5d,0x59,0x56,0x52,0x54,0x55,0x53,0x50,0x4e,0x46,0x44, + 0x7e,0x8c,0x8f,0x8d,0x85,0x74,0x6c,0x6b,0x75,0x76,0x7f,0x85,0x8f,0x95,0x97,0x99, + 0x99,0x99,0x98,0x98,0x98,0x98,0x97,0x99,0x9b,0x9e,0x9f,0xa0,0xa0,0xa0,0xa0,0x9f, + 0x9d,0x9e,0x9e,0xa0,0xa1,0x9e,0x9b,0xa0,0xa4,0xa6,0x9c,0x8a,0x81,0x76,0x77,0x7b, + 0x79,0x76,0x73,0x70,0x6f,0x7c,0x9e,0xb5,0xbc,0xba,0xac,0x96,0x83,0x79,0x77,0x77, + 0x74,0x71,0x6f,0x66,0x67,0x66,0x66,0x65,0x60,0x5e,0x61,0x68,0x6e,0x6f,0x72,0x71, + 0x6d,0x62,0x5e,0x5e,0x5b,0x5a,0x5d,0x60,0x59,0x56,0x4e,0x45,0x3b,0x35,0x34,0x34, + 0x34,0x38,0x42,0x49,0x49,0x4a,0x4c,0x50,0x51,0x53,0x54,0x4c,0x46,0x46,0x46,0x43, + 0x48,0x4d,0x53,0x56,0x5a,0x5a,0x57,0x56,0x55,0x55,0x57,0x52,0x4f,0x48,0x3f,0x3f, + 0x80,0x8f,0x8e,0x8d,0x83,0x73,0x6a,0x6e,0x76,0x78,0x81,0x88,0x91,0x97,0x99,0x99, + 0x99,0x99,0x99,0x99,0x98,0x98,0x9a,0x9b,0x9d,0x9f,0xa0,0xa0,0xa0,0xa0,0xa0,0x9e, + 0x9d,0x9d,0xa0,0xa0,0xa2,0x9e,0x9f,0xa5,0xa9,0xaa,0xa4,0x97,0x8d,0x81,0x79,0x77, + 0x78,0x75,0x71,0x70,0x70,0x75,0x86,0x9b,0xa3,0x9d,0x90,0x7d,0x6a,0x69,0x71,0x72, + 0x73,0x73,0x6f,0x6d,0x64,0x60,0x5d,0x59,0x55,0x54,0x59,0x62,0x67,0x6a,0x6d,0x6a, + 0x64,0x5d,0x59,0x55,0x51,0x50,0x53,0x57,0x54,0x53,0x4a,0x43,0x41,0x3b,0x39,0x3b, + 0x3b,0x3c,0x42,0x47,0x45,0x46,0x48,0x4c,0x4e,0x4f,0x4e,0x4b,0x48,0x44,0x45,0x3f, + 0x41,0x49,0x4d,0x4d,0x52,0x51,0x50,0x4e,0x4d,0x57,0x5a,0x58,0x54,0x4d,0x3c,0x33, + 0x8a,0x8f,0x8d,0x8c,0x84,0x74,0x6b,0x71,0x76,0x7b,0x82,0x8b,0x94,0x96,0x98,0x9a, + 0x9a,0x9a,0x99,0x98,0x98,0x9a,0x9b,0x9c,0x9f,0xa0,0xa1,0xa1,0xa1,0xa0,0x9f,0x9e, + 0x9d,0x9e,0xa1,0xa2,0xa4,0x9e,0x9f,0xa3,0xaa,0xac,0xa9,0xa4,0x99,0x8e,0x80,0x79, + 0x76,0x74,0x6f,0x6e,0x6f,0x70,0x75,0x80,0x86,0x7e,0x72,0x66,0x61,0x5f,0x64,0x6d, + 0x6f,0x70,0x6d,0x69,0x67,0x5b,0x51,0x4c,0x48,0x4a,0x51,0x5d,0x64,0x68,0x6a,0x64, + 0x5e,0x53,0x4d,0x45,0x42,0x45,0x49,0x4c,0x4f,0x50,0x4b,0x47,0x43,0x41,0x40,0x41, + 0x44,0x44,0x45,0x47,0x45,0x44,0x44,0x46,0x4b,0x4b,0x4c,0x4e,0x4c,0x48,0x45,0x3e, + 0x3e,0x45,0x48,0x46,0x48,0x4a,0x48,0x48,0x4a,0x56,0x5d,0x5d,0x56,0x4a,0x3a,0x2b, + 0x8d,0x8c,0x8b,0x8a,0x83,0x76,0x6e,0x71,0x76,0x7f,0x84,0x8f,0x95,0x97,0x98,0x9a, + 0x9a,0x9a,0x99,0x99,0x9a,0x9b,0x9d,0x9e,0x9f,0xa0,0xa1,0xa1,0xa1,0xa0,0x9f,0xa0, + 0xa0,0xa0,0xa1,0xa2,0xa3,0xa0,0xa0,0xa3,0xa8,0xae,0xb0,0xae,0xa5,0x9a,0x8d,0x81, + 0x75,0x72,0x70,0x6d,0x6d,0x71,0x77,0x7c,0x77,0x6e,0x65,0x5e,0x5f,0x5c,0x5a,0x5f, + 0x6a,0x6c,0x69,0x67,0x68,0x63,0x52,0x43,0x3c,0x3f,0x48,0x56,0x62,0x68,0x65,0x5b, + 0x51,0x49,0x3e,0x32,0x2e,0x37,0x40,0x3f,0x46,0x4b,0x4b,0x45,0x45,0x47,0x45,0x48, + 0x4a,0x47,0x42,0x41,0x3e,0x3b,0x3d,0x40,0x45,0x4b,0x4f,0x53,0x51,0x4d,0x48,0x41, + 0x3d,0x40,0x3d,0x3d,0x3c,0x3a,0x3e,0x4a,0x51,0x59,0x61,0x5f,0x58,0x4d,0x3c,0x2e, + 0x8f,0x8c,0x8a,0x8c,0x86,0x7b,0x74,0x74,0x78,0x80,0x87,0x92,0x97,0x99,0x9a,0x9b, + 0x9a,0x9a,0x99,0x9b,0x9c,0x9d,0x9e,0x9d,0x9e,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0, + 0x9f,0x9f,0xa1,0xa2,0xa4,0xa2,0xa1,0xa4,0xa9,0xb0,0xb2,0xb3,0xaf,0xa5,0x99,0x8c, + 0x7e,0x73,0x6f,0x6d,0x6e,0x77,0x83,0x85,0x85,0x7f,0x70,0x67,0x65,0x62,0x5f,0x5e, + 0x5f,0x67,0x6a,0x68,0x67,0x64,0x5f,0x4f,0x3e,0x3f,0x48,0x57,0x63,0x66,0x65,0x57, + 0x4a,0x40,0x33,0x29,0x25,0x2b,0x38,0x3e,0x47,0x4b,0x4b,0x45,0x45,0x46,0x43,0x46, + 0x47,0x45,0x3e,0x3c,0x39,0x37,0x3b,0x3f,0x47,0x4f,0x57,0x55,0x51,0x4f,0x4a,0x44, + 0x3e,0x3b,0x3a,0x37,0x36,0x37,0x3e,0x49,0x55,0x56,0x5b,0x5a,0x52,0x4c,0x3d,0x30, + 0x8e,0x89,0x8b,0x8b,0x8a,0x80,0x7c,0x77,0x79,0x82,0x8a,0x93,0x97,0x99,0x9a,0x9a, + 0x9b,0x9c,0x9b,0x9b,0x9c,0x9d,0x9d,0x9e,0x9e,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1, + 0xa2,0xa1,0xa1,0xa2,0xa3,0xa3,0xa2,0xa6,0xab,0xb0,0xb4,0xb3,0xb3,0xaf,0xa6,0x98, + 0x89,0x79,0x70,0x6e,0x6e,0x7a,0x87,0x89,0x85,0x83,0x7d,0x71,0x6b,0x69,0x66,0x64, + 0x63,0x62,0x69,0x6d,0x6b,0x65,0x5f,0x58,0x4e,0x42,0x47,0x58,0x62,0x67,0x61,0x58, + 0x4b,0x38,0x2b,0x27,0x24,0x2a,0x36,0x44,0x4b,0x4e,0x4d,0x48,0x46,0x44,0x42,0x43, + 0x43,0x41,0x3c,0x3a,0x38,0x36,0x3b,0x48,0x50,0x57,0x5e,0x60,0x5c,0x55,0x4f,0x48, + 0x42,0x3e,0x3b,0x38,0x35,0x34,0x3e,0x49,0x53,0x55,0x56,0x52,0x49,0x40,0x36,0x2a, + 0x8d,0x8c,0x8a,0x8d,0x93,0x84,0x7b,0x78,0x7a,0x85,0x8e,0x93,0x98,0x99,0x9a,0x9b, + 0x9a,0x9b,0x9b,0x9c,0x9d,0x9d,0x9e,0x9e,0x9f,0xa0,0xa0,0xa0,0xa2,0xa2,0xa1,0xa1, + 0xa1,0xa1,0xa0,0xa0,0xa1,0xa3,0xa2,0xa7,0xac,0xaf,0xb4,0xb5,0xb5,0xb5,0xb0,0xa3, + 0x94,0x85,0x74,0x6e,0x6e,0x75,0x83,0x83,0x81,0x7b,0x77,0x71,0x70,0x6c,0x69,0x67, + 0x67,0x64,0x63,0x67,0x6e,0x6a,0x6a,0x62,0x56,0x4e,0x47,0x51,0x5f,0x65,0x63,0x5b, + 0x4d,0x3d,0x2c,0x26,0x25,0x29,0x36,0x43,0x4c,0x4d,0x4c,0x49,0x45,0x3f,0x41,0x3e, + 0x3c,0x39,0x39,0x38,0x35,0x36,0x3d,0x4c,0x56,0x5d,0x63,0x64,0x64,0x57,0x4d,0x48, + 0x45,0x44,0x3f,0x3e,0x39,0x38,0x44,0x4c,0x50,0x50,0x50,0x48,0x3d,0x31,0x2a,0x23, + 0x8d,0x8e,0x8e,0x90,0x93,0x7f,0x76,0x73,0x7f,0x88,0x8f,0x95,0x97,0x99,0x9a,0x9a, + 0x9a,0x9b,0x9c,0x9d,0x9e,0x9e,0x9e,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1,0xa1, + 0xa2,0xa2,0xa2,0xa2,0xa2,0xa3,0xa2,0xa6,0xac,0xb0,0xb4,0xb6,0xb6,0xb5,0xb3,0xad, + 0xa1,0x90,0x7e,0x70,0x6d,0x70,0x75,0x7a,0x76,0x74,0x75,0x78,0x7b,0x7c,0x7d,0x7e, + 0x80,0x82,0x84,0x87,0x8e,0x98,0x9c,0x93,0x7c,0x63,0x53,0x4f,0x5b,0x64,0x64,0x5d, + 0x54,0x43,0x32,0x29,0x26,0x2e,0x35,0x41,0x4d,0x4d,0x4c,0x49,0x45,0x40,0x3b,0x39, + 0x38,0x37,0x37,0x39,0x38,0x3c,0x45,0x4e,0x59,0x62,0x65,0x67,0x63,0x58,0x4f,0x4c, + 0x4b,0x48,0x47,0x44,0x41,0x41,0x47,0x4e,0x50,0x50,0x49,0x3e,0x31,0x27,0x21,0x1f, + 0x8f,0x90,0x92,0x8f,0x8c,0x76,0x70,0x71,0x82,0x8c,0x91,0x95,0x97,0x99,0x9a,0x9a, + 0x9a,0x9c,0x9d,0x9d,0x9e,0x9e,0x9e,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa1,0xa0,0xa1, + 0xa2,0xa2,0xa2,0xa2,0xa3,0xa4,0xa4,0xa7,0xac,0xaf,0xb4,0xb6,0xb6,0xb5,0xb4,0xb1, + 0xab,0x9d,0x8b,0x78,0x6f,0x72,0x7a,0x8e,0x9b,0xa2,0xa6,0xa7,0xab,0xae,0xb1,0xb4, + 0xb5,0xb7,0xb9,0xbb,0xba,0xc1,0xc3,0xb4,0x9d,0x82,0x65,0x51,0x5b,0x64,0x65,0x5c, + 0x53,0x46,0x37,0x28,0x24,0x2b,0x2f,0x3e,0x49,0x4b,0x4a,0x45,0x43,0x40,0x3d,0x3c, + 0x3c,0x3a,0x3a,0x3b,0x3d,0x43,0x4d,0x53,0x59,0x62,0x69,0x6b,0x64,0x5b,0x54,0x50, + 0x4f,0x50,0x51,0x4e,0x4b,0x4a,0x4b,0x4d,0x4e,0x49,0x40,0x37,0x2d,0x23,0x1e,0x1e, + 0x8f,0x92,0x91,0x8e,0x88,0x70,0x6b,0x71,0x85,0x8e,0x93,0x96,0x98,0x9a,0x9b,0x9c, + 0x9b,0x9c,0x9d,0x9e,0x9e,0x9e,0x9e,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1,0xa0, + 0xa1,0xa2,0xa2,0xa2,0xa3,0xa4,0xa5,0xa7,0xab,0xaf,0xb3,0xb6,0xb6,0xb6,0xb6,0xb4, + 0xb1,0xa6,0x94,0x82,0x73,0x7c,0x99,0xb1,0xc0,0xc9,0xca,0xcb,0xcd,0xd0,0xd2,0xd3, + 0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xcd,0xb6,0x95,0x6f,0x57,0x5c,0x62,0x62,0x5b, + 0x52,0x43,0x33,0x26,0x1d,0x22,0x29,0x36,0x41,0x46,0x43,0x41,0x40,0x3e,0x3e,0x3e, + 0x3f,0x3e,0x3e,0x41,0x43,0x4a,0x4e,0x52,0x57,0x5b,0x63,0x66,0x64,0x5b,0x56,0x54, + 0x54,0x56,0x59,0x55,0x54,0x56,0x54,0x4f,0x4d,0x44,0x39,0x30,0x2b,0x21,0x1d,0x1d, + 0x91,0x8f,0x8d,0x8d,0x83,0x6d,0x6c,0x74,0x8b,0x8f,0x95,0x97,0x99,0x99,0x9b,0x9c, + 0x9c,0x9c,0x9c,0x9c,0x9e,0x9e,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xaa,0xae,0xb2,0xb4,0xb5,0xb7,0xb6,0xb6, + 0xb5,0xae,0x9e,0x8c,0x7a,0x8e,0xb5,0xca,0xd9,0xdd,0xdc,0xdd,0xdf,0xe0,0xe3,0xe2, + 0xe4,0xe5,0xe5,0xe5,0xe6,0xe7,0xe4,0xd8,0xc2,0xa6,0x77,0x5c,0x60,0x61,0x5d,0x59, + 0x4f,0x40,0x31,0x26,0x1d,0x21,0x2a,0x35,0x3e,0x40,0x3d,0x3e,0x39,0x39,0x3d,0x3f, + 0x3e,0x42,0x42,0x46,0x4c,0x4f,0x4e,0x4e,0x53,0x57,0x5b,0x5e,0x5d,0x57,0x58,0x57, + 0x58,0x5a,0x5d,0x5a,0x5c,0x5f,0x5c,0x54,0x4c,0x42,0x33,0x2b,0x26,0x1f,0x1c,0x1b, + 0x92,0x8f,0x88,0x8a,0x80,0x6f,0x71,0x7d,0x91,0x94,0x97,0x98,0x99,0x9a,0x9c,0x9a, + 0x9c,0x9d,0x9f,0x9e,0x9f,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa2,0xa4,0xa4,0xa5,0xa6,0xaa,0xad,0xb1,0xb3,0xb4,0xb5,0xb8,0xb7, + 0xb5,0xb2,0xa5,0x95,0x86,0x98,0xba,0xd1,0xdb,0xde,0xdf,0xdd,0xe1,0xe2,0xe5,0xe5, + 0xe7,0xe7,0xe7,0xe6,0xe5,0xe2,0xe1,0xd5,0xc2,0xa7,0x7e,0x65,0x63,0x56,0x52,0x4f, + 0x46,0x37,0x2b,0x25,0x1f,0x21,0x29,0x34,0x3b,0x3b,0x36,0x36,0x35,0x37,0x39,0x3e, + 0x43,0x44,0x45,0x47,0x4b,0x4d,0x4d,0x4d,0x4e,0x53,0x52,0x52,0x55,0x54,0x55,0x57, + 0x5d,0x60,0x62,0x60,0x60,0x61,0x5e,0x55,0x4b,0x41,0x31,0x27,0x23,0x1d,0x1c,0x1c, + 0x95,0x8d,0x84,0x88,0x81,0x84,0x89,0x90,0x97,0x99,0x9a,0x9c,0x9d,0x9d,0x9d,0x9e, + 0x9e,0x9e,0x9e,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa2,0xa4,0xa4,0xa4,0xa6,0xa9,0xad,0xaf,0xb3,0xb3,0xb4,0xb6,0xb9, + 0xb6,0xb4,0xad,0x9f,0x90,0x9e,0xb6,0xc7,0xcf,0xcb,0xc6,0xc6,0xc9,0xd1,0xd5,0xd8, + 0xda,0xdb,0xda,0xd7,0xd4,0xd4,0xd5,0xc8,0xb8,0xa3,0x85,0x68,0x62,0x51,0x45,0x41, + 0x38,0x2c,0x23,0x23,0x1e,0x22,0x29,0x32,0x37,0x37,0x2f,0x2d,0x29,0x2e,0x34,0x3a, + 0x3f,0x45,0x47,0x48,0x49,0x4b,0x4c,0x4c,0x4c,0x4e,0x51,0x4e,0x55,0x53,0x55,0x58, + 0x5e,0x61,0x61,0x61,0x62,0x62,0x61,0x59,0x4a,0x3d,0x2f,0x24,0x1e,0x1c,0x1b,0x20, + 0x96,0x8b,0x82,0x86,0x8c,0x88,0x8a,0x98,0x9d,0x9c,0x9d,0x9d,0x9e,0x9f,0x9f,0xa0, + 0xa0,0xa0,0xa0,0xa1,0xa0,0xa0,0x9f,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xab,0xac,0xae,0xb1,0xb2,0xb2,0xb4,0xb7, + 0xb7,0xb4,0xb0,0xa3,0x95,0x9f,0xae,0xb6,0xb1,0xa1,0x95,0x91,0x98,0xa1,0xab,0xb5, + 0xb8,0xba,0xb7,0xb2,0xad,0xad,0xaf,0xa1,0x9c,0x91,0x7c,0x68,0x5e,0x49,0x3b,0x30, + 0x27,0x22,0x1f,0x1d,0x1a,0x20,0x27,0x2c,0x2d,0x2e,0x29,0x22,0x21,0x27,0x2e,0x34, + 0x3c,0x42,0x47,0x49,0x4d,0x4d,0x4a,0x49,0x49,0x4a,0x4d,0x50,0x52,0x50,0x4f,0x55, + 0x5b,0x5c,0x5d,0x5d,0x5e,0x5b,0x5e,0x5a,0x4f,0x3d,0x31,0x29,0x1f,0x1d,0x1c,0x22, + 0x95,0x8b,0x85,0x8a,0x8e,0x8c,0x91,0x97,0x9d,0x9e,0x9d,0x9f,0xa0,0x9f,0x9f,0xa0, + 0xa0,0xa0,0xa1,0xa1,0xa0,0xa1,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xaa,0xac,0xaf,0xb1,0xb1,0xb2,0xb3,0xb6, + 0xb6,0xb4,0xb1,0xa9,0x9f,0xa4,0xa8,0xa2,0x96,0x7b,0x5e,0x50,0x59,0x62,0x71,0x82, + 0x89,0x88,0x80,0x7a,0x72,0x73,0x7b,0x79,0x7e,0x7b,0x74,0x69,0x5c,0x42,0x31,0x22, + 0x1b,0x1b,0x1b,0x18,0x16,0x1c,0x23,0x25,0x27,0x26,0x22,0x1e,0x1f,0x26,0x2f,0x38, + 0x40,0x46,0x4c,0x4e,0x4f,0x49,0x44,0x46,0x49,0x49,0x4d,0x4f,0x52,0x4e,0x4b,0x50, + 0x5c,0x5a,0x5a,0x5b,0x5a,0x53,0x54,0x57,0x50,0x41,0x36,0x2c,0x23,0x21,0x20,0x27, + 0x90,0x88,0x84,0x88,0x82,0x84,0x89,0x94,0x9d,0x9c,0x9c,0x9f,0x9f,0x9f,0xa0,0xa0, + 0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa2,0xa1,0xa1,0xa1,0xa0,0xa2,0xa3,0xa3,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa3,0xa3,0xa5,0xa5,0xa6,0xaa,0xad,0xae,0xaf,0xb0,0xb0,0xb3,0xb3, + 0xb4,0xb4,0xb1,0xaf,0xa6,0xa3,0x99,0x85,0x72,0x58,0x3b,0x29,0x27,0x29,0x33,0x40, + 0x4a,0x4b,0x44,0x36,0x2f,0x2c,0x33,0x44,0x5d,0x6b,0x6e,0x6c,0x5c,0x40,0x2e,0x1e, + 0x16,0x16,0x15,0x14,0x13,0x1c,0x21,0x25,0x25,0x23,0x20,0x1f,0x21,0x26,0x2d,0x3a, + 0x48,0x50,0x51,0x52,0x51,0x4a,0x41,0x3e,0x45,0x43,0x46,0x48,0x49,0x46,0x48,0x4d, + 0x58,0x59,0x55,0x53,0x53,0x4e,0x4e,0x50,0x4f,0x43,0x39,0x2c,0x25,0x22,0x22,0x26, + 0x8a,0x87,0x88,0x8b,0x83,0x81,0x89,0x94,0x9b,0x9c,0x9b,0x9d,0x9f,0x9f,0xa0,0xa0, + 0xa0,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa1,0xa1,0xa1,0xa0,0xa3,0xa4,0xa3,0xa3,0xa2, + 0xa2,0xa2,0xa2,0xa3,0xa3,0xa5,0xa5,0xa6,0xaa,0xac,0xae,0xae,0xae,0xaf,0xb2,0xb3, + 0xb3,0xb4,0xb2,0xaf,0xa8,0x9b,0x87,0x6c,0x53,0x3d,0x2e,0x27,0x22,0x20,0x2b,0x36, + 0x41,0x43,0x3d,0x30,0x27,0x22,0x23,0x30,0x55,0x69,0x74,0x73,0x63,0x45,0x32,0x21, + 0x16,0x13,0x13,0x15,0x14,0x1c,0x24,0x27,0x25,0x25,0x24,0x22,0x24,0x2b,0x33,0x3f, + 0x4b,0x52,0x56,0x56,0x55,0x4d,0x41,0x3a,0x3d,0x3a,0x3c,0x3e,0x3d,0x3b,0x41,0x47, + 0x50,0x57,0x53,0x4e,0x4d,0x4a,0x4c,0x4b,0x47,0x41,0x38,0x2c,0x22,0x21,0x23,0x24, + 0x86,0x83,0x82,0x8b,0x84,0x83,0x8a,0x95,0x9c,0x9b,0x9a,0x9c,0x9f,0xa0,0xa0,0x9f, + 0x9f,0x9f,0x9f,0x9f,0x9f,0xa0,0xa0,0xa1,0xa2,0xa1,0xa1,0xa1,0xa2,0xa2,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xa9,0xac,0xac,0xad,0xae,0xb0,0xb2,0xb2, + 0xb3,0xb1,0xaf,0xaa,0xa1,0x8b,0x76,0x56,0x36,0x2e,0x2b,0x27,0x1e,0x1b,0x27,0x33, + 0x37,0x38,0x35,0x2d,0x26,0x21,0x27,0x34,0x5b,0x70,0x79,0x76,0x69,0x48,0x36,0x25, + 0x1a,0x14,0x14,0x15,0x15,0x1a,0x20,0x25,0x25,0x26,0x25,0x23,0x28,0x31,0x3a,0x44, + 0x4c,0x50,0x56,0x52,0x4e,0x45,0x3a,0x36,0x37,0x38,0x3b,0x3b,0x32,0x33,0x3a,0x41, + 0x4c,0x51,0x52,0x4e,0x46,0x43,0x42,0x42,0x42,0x3e,0x37,0x2c,0x22,0x20,0x23,0x22, + 0x88,0x86,0x85,0x87,0x82,0x82,0x8c,0x97,0x9d,0x9b,0x9a,0x9c,0x9e,0x9f,0xa0,0x9f, + 0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0,0xa1,0xa1,0xa1,0xa1,0xa1,0xa2,0xa2,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xa9,0xac,0xac,0xad,0xae,0xb0,0xb1,0xb2, + 0xb0,0xac,0xa7,0xa1,0x9a,0x82,0x65,0x3f,0x2a,0x2a,0x2b,0x26,0x20,0x1a,0x20,0x2b, + 0x2f,0x31,0x29,0x24,0x22,0x22,0x2a,0x39,0x60,0x71,0x7a,0x7a,0x6c,0x4d,0x3b,0x2b, + 0x1e,0x15,0x13,0x14,0x15,0x19,0x20,0x23,0x25,0x27,0x25,0x24,0x28,0x33,0x3c,0x45, + 0x4c,0x51,0x54,0x50,0x47,0x3e,0x37,0x33,0x31,0x37,0x36,0x35,0x32,0x33,0x3b,0x46, + 0x4f,0x52,0x54,0x47,0x3d,0x38,0x37,0x38,0x3a,0x3a,0x37,0x2c,0x24,0x1f,0x20,0x21, + 0x87,0x88,0x88,0x8d,0x82,0x85,0x8d,0x98,0x9f,0x9b,0x9c,0x9b,0x9d,0x9e,0xa0,0xa0, + 0xa0,0xa0,0x9f,0x9f,0x9e,0x9f,0x9f,0xa0,0xa1,0xa0,0xa0,0xa0,0xa1,0xa1,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa2,0xa4,0xa4,0xa5,0xa7,0xa9,0xab,0xac,0xad,0xae,0xb0,0xb1,0xb1, + 0xb0,0xab,0xa0,0x9a,0x94,0x83,0x6c,0x48,0x31,0x27,0x27,0x20,0x1b,0x19,0x17,0x20, + 0x29,0x28,0x22,0x1c,0x1d,0x1e,0x27,0x38,0x5f,0x70,0x77,0x75,0x6c,0x51,0x42,0x32, + 0x23,0x19,0x15,0x17,0x16,0x19,0x21,0x24,0x26,0x26,0x24,0x22,0x28,0x30,0x3a,0x43, + 0x49,0x4e,0x4c,0x47,0x3c,0x38,0x34,0x35,0x35,0x37,0x36,0x36,0x37,0x39,0x3f,0x4a, + 0x51,0x55,0x4f,0x46,0x3a,0x32,0x2e,0x31,0x34,0x35,0x32,0x2c,0x29,0x22,0x21,0x20, + 0x8c,0x8f,0x8d,0x8b,0x7f,0x82,0x8a,0x99,0xa1,0x9e,0x9c,0x9b,0x9c,0x9f,0x9f,0xa2, + 0xa2,0xa2,0xa2,0xa0,0xa0,0xa0,0x9f,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa2,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa3,0xa3,0xa5,0xa5,0xa7,0xa9,0xab,0xad,0xae,0xae,0xaf,0xb0,0xb1, + 0xb0,0xab,0x9e,0x94,0x8f,0x88,0x72,0x55,0x39,0x27,0x21,0x1a,0x13,0x10,0x15,0x1b, + 0x20,0x21,0x1c,0x1a,0x1e,0x1f,0x28,0x3b,0x5b,0x6a,0x71,0x70,0x68,0x57,0x48,0x3c, + 0x2c,0x20,0x1a,0x19,0x19,0x20,0x28,0x28,0x28,0x27,0x27,0x28,0x2d,0x32,0x3d,0x46, + 0x49,0x4b,0x4a,0x42,0x39,0x33,0x2f,0x30,0x37,0x38,0x3a,0x3b,0x44,0x43,0x47,0x4e, + 0x54,0x53,0x4a,0x42,0x35,0x29,0x26,0x28,0x2e,0x2f,0x2e,0x2e,0x2c,0x29,0x26,0x24, + 0x8f,0x92,0x8d,0x8b,0x7e,0x81,0x89,0x9a,0xa0,0xa0,0x9b,0x9b,0x9b,0x9e,0xa0,0xa1, + 0xa1,0xa0,0xa1,0xa1,0xa0,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0,0xa0,0xa1,0xa2,0xa2,0xa2, + 0xa2,0xa2,0xa2,0xa3,0xa3,0xa5,0xa5,0xa7,0xa9,0xab,0xad,0xae,0xae,0xaf,0xb0,0xb1, + 0xaf,0xaa,0x9e,0x92,0x8d,0x89,0x79,0x61,0x50,0x40,0x39,0x25,0x11,0x0d,0x12,0x19, + 0x1f,0x21,0x21,0x1f,0x21,0x22,0x2a,0x3c,0x56,0x63,0x6e,0x6a,0x61,0x55,0x4b,0x3f, + 0x31,0x25,0x1d,0x1c,0x1e,0x22,0x27,0x28,0x28,0x28,0x2c,0x2e,0x30,0x39,0x42,0x49, + 0x4b,0x4d,0x4c,0x46,0x3d,0x36,0x2e,0x30,0x38,0x38,0x3a,0x3c,0x43,0x47,0x47,0x4f, + 0x50,0x4e,0x46,0x3d,0x30,0x26,0x22,0x25,0x29,0x2a,0x2c,0x2d,0x30,0x2c,0x29,0x26, + 0x93,0x92,0x8c,0x88,0x7b,0x82,0x8d,0x9a,0xa0,0xa0,0x9d,0x9c,0x9c,0x9d,0x9e,0xa1, + 0xa1,0xa2,0xa1,0xa1,0xa1,0xa0,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa2,0xa2,0xa2, + 0xa2,0xa1,0xa2,0xa2,0xa3,0xa4,0xa5,0xa6,0xa9,0xab,0xac,0xad,0xaf,0xb0,0xb2,0xb3, + 0xb1,0xab,0x9e,0x92,0x8b,0x88,0x83,0x78,0x78,0x75,0x61,0x44,0x23,0x11,0x15,0x1d, + 0x24,0x27,0x29,0x25,0x25,0x25,0x2b,0x3a,0x52,0x60,0x67,0x65,0x5e,0x59,0x51,0x46, + 0x38,0x2c,0x24,0x22,0x23,0x26,0x27,0x2a,0x2c,0x2f,0x33,0x33,0x36,0x3f,0x45,0x4c, + 0x51,0x52,0x50,0x4b,0x42,0x3d,0x38,0x34,0x36,0x39,0x37,0x36,0x3b,0x41,0x43,0x46, + 0x46,0x43,0x3c,0x35,0x2c,0x25,0x21,0x24,0x28,0x2e,0x2f,0x32,0x32,0x2e,0x30,0x2e, + 0x90,0x8d,0x88,0x83,0x79,0x83,0x8e,0x9b,0xa0,0xa0,0x9e,0x9c,0x9b,0x9b,0x9c,0xa0, + 0xa1,0xa1,0xa1,0xa1,0xa1,0xa1,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa2,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xaa,0xac,0xac,0xad,0xaf,0xb0,0xb3,0xb3, + 0xb1,0xab,0x9d,0x8f,0x87,0x89,0x8d,0x91,0x9d,0x97,0x7d,0x5d,0x38,0x1e,0x1b,0x26, + 0x2c,0x31,0x31,0x2b,0x2a,0x25,0x29,0x3b,0x51,0x5d,0x62,0x62,0x5c,0x5a,0x54,0x4b, + 0x3e,0x35,0x2d,0x26,0x26,0x29,0x29,0x2b,0x2e,0x31,0x31,0x35,0x3a,0x47,0x4c,0x51, + 0x53,0x57,0x56,0x4d,0x47,0x40,0x3b,0x33,0x33,0x34,0x33,0x33,0x35,0x37,0x3c,0x3a, + 0x37,0x31,0x2e,0x29,0x26,0x24,0x21,0x25,0x29,0x2d,0x30,0x35,0x37,0x33,0x33,0x32, + 0x8c,0x87,0x7f,0x7e,0x7b,0x85,0x90,0x9c,0x9f,0x9e,0x9e,0x9c,0x9b,0x9a,0x9d,0x9e, + 0x9f,0xa0,0xa1,0xa1,0xa1,0xa1,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa1,0xa2,0xa1,0xa1, + 0xa1,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xaa,0xac,0xac,0xad,0xaf,0xb0,0xb3,0xb2, + 0xb3,0xad,0x9d,0x8e,0x88,0x87,0x93,0xa4,0xb1,0xa9,0x90,0x6d,0x4c,0x2f,0x2c,0x30, + 0x38,0x3f,0x3a,0x32,0x2c,0x26,0x29,0x3b,0x50,0x5c,0x62,0x61,0x5b,0x5b,0x57,0x4f, + 0x45,0x3d,0x32,0x2b,0x2a,0x2e,0x2e,0x2c,0x2a,0x2e,0x34,0x3a,0x40,0x4a,0x50,0x53, + 0x55,0x58,0x5a,0x51,0x4b,0x45,0x40,0x37,0x34,0x30,0x30,0x31,0x31,0x33,0x34,0x30, + 0x2c,0x27,0x25,0x21,0x20,0x22,0x24,0x28,0x2e,0x31,0x36,0x39,0x3b,0x39,0x36,0x37, + 0x8b,0x86,0x7a,0x79,0x7b,0x87,0x90,0x9a,0x9d,0x9e,0x9e,0x9c,0x9b,0x9b,0x9b,0x9e, + 0xa0,0x9f,0xa0,0xa1,0xa1,0xa0,0x9e,0x9f,0x9f,0xa0,0xa0,0xa2,0xa2,0xa2,0xa1,0xa1, + 0xa1,0xa1,0xa1,0xa1,0xa4,0xa5,0xa7,0xa8,0xab,0xac,0xac,0xad,0xaf,0xb0,0xb3,0xb3, + 0xb2,0xae,0xa2,0x97,0x8d,0x89,0x90,0xa6,0xb1,0xad,0x99,0x73,0x4f,0x3a,0x3a,0x3d, + 0x3f,0x43,0x3d,0x34,0x2a,0x24,0x27,0x3a,0x4f,0x5b,0x61,0x60,0x5b,0x5a,0x5a,0x53, + 0x4b,0x43,0x3b,0x35,0x32,0x34,0x2e,0x2b,0x28,0x2c,0x31,0x37,0x42,0x48,0x4f,0x53, + 0x56,0x57,0x56,0x51,0x4c,0x46,0x41,0x39,0x37,0x30,0x2c,0x2b,0x2b,0x2d,0x2b,0x2a, + 0x26,0x21,0x1d,0x1a,0x1c,0x1e,0x25,0x2a,0x31,0x35,0x39,0x3b,0x3e,0x3c,0x3a,0x3a, + 0x8e,0x86,0x77,0x77,0x78,0x89,0x90,0x98,0x9c,0x9c,0x9e,0x9d,0x9d,0x9c,0x9b,0x9c, + 0x9c,0x9d,0x9f,0x9f,0xa0,0x9f,0x9f,0x9f,0x9f,0xa0,0x9f,0xa1,0xa1,0xa1,0xa1,0xa1, + 0xa1,0xa1,0xa1,0xa2,0xa4,0xa5,0xa7,0xa8,0xab,0xac,0xac,0xad,0xaf,0xb0,0xb3,0xb3, + 0xb2,0xb1,0xa9,0x9a,0x8e,0x8b,0x8f,0xa1,0xa8,0xa7,0x96,0x6c,0x46,0x45,0x49,0x47, + 0x47,0x43,0x3b,0x34,0x29,0x24,0x26,0x3b,0x4e,0x5a,0x5f,0x5f,0x5a,0x59,0x5d,0x59, + 0x54,0x4a,0x43,0x3e,0x39,0x38,0x33,0x2f,0x2d,0x31,0x37,0x3b,0x43,0x47,0x4d,0x53, + 0x57,0x58,0x54,0x4f,0x4d,0x44,0x41,0x3a,0x35,0x2f,0x29,0x27,0x28,0x28,0x2a,0x2b, + 0x28,0x24,0x1e,0x18,0x19,0x1d,0x21,0x26,0x2c,0x34,0x39,0x3d,0x3e,0x40,0x3e,0x3e, + 0x92,0x88,0x79,0x75,0x7c,0x89,0x90,0x97,0x9b,0x9c,0x9c,0x9d,0x9d,0x9d,0x9c,0x9a, + 0x9b,0x9d,0x9e,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0,0xa1, + 0xa1,0xa1,0xa1,0xa1,0xa4,0xa5,0xa7,0xa8,0xab,0xac,0xac,0xad,0xaf,0xb0,0xb3,0xb3, + 0xb4,0xb2,0xa9,0x9c,0x92,0x88,0x8a,0x9b,0xa7,0xa7,0xa1,0x80,0x63,0x5a,0x54,0x4b, + 0x46,0x43,0x3c,0x36,0x2e,0x22,0x25,0x3c,0x4f,0x5a,0x5e,0x5c,0x57,0x5a,0x5d,0x5c, + 0x55,0x4f,0x48,0x44,0x3b,0x38,0x36,0x35,0x32,0x36,0x3b,0x3e,0x46,0x49,0x4c,0x51, + 0x53,0x51,0x51,0x4f,0x4a,0x42,0x3c,0x38,0x34,0x2a,0x27,0x28,0x27,0x2a,0x2d,0x2e, + 0x2a,0x27,0x22,0x1a,0x16,0x19,0x1f,0x24,0x28,0x2f,0x33,0x38,0x3b,0x3a,0x3b,0x3c, + 0x94,0x8a,0x76,0x7a,0x85,0x90,0x93,0x97,0x9a,0x9b,0x9c,0x9d,0x9d,0x9f,0x9d,0x9c, + 0x9a,0x9c,0x9d,0x9e,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0, + 0xa1,0xa1,0xa1,0xa1,0xa3,0xa4,0xa6,0xa8,0xaa,0xab,0xad,0xae,0xaf,0xb0,0xb3,0xb3, + 0xb3,0xb1,0xa6,0x99,0x8d,0x85,0x8b,0x9f,0xac,0xae,0xae,0x9e,0x8b,0x74,0x60,0x4f, + 0x48,0x45,0x3f,0x35,0x2c,0x24,0x2a,0x3e,0x50,0x59,0x5e,0x5c,0x59,0x5b,0x5d,0x5d, + 0x58,0x53,0x4d,0x46,0x3e,0x38,0x35,0x35,0x36,0x39,0x3d,0x41,0x47,0x4a,0x4c,0x4d, + 0x50,0x4e,0x4e,0x4d,0x44,0x3d,0x3b,0x37,0x33,0x28,0x26,0x26,0x25,0x29,0x30,0x33, + 0x32,0x31,0x2b,0x20,0x1b,0x1d,0x21,0x24,0x26,0x2c,0x30,0x33,0x34,0x37,0x37,0x35, + 0x95,0x85,0x74,0x7e,0x8b,0x96,0x97,0x96,0x99,0x99,0x9a,0x9d,0x9d,0x9f,0x9f,0x9e, + 0x9b,0x9b,0x9a,0x9d,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0,0xa0,0xa0,0xa0, + 0xa1,0xa1,0xa1,0xa1,0xa3,0xa4,0xa6,0xa7,0xaa,0xab,0xad,0xae,0xaf,0xb0,0xb3,0xb3, + 0xb3,0xb0,0xa3,0x97,0x8b,0x85,0x8b,0xa2,0xb6,0xbd,0xc0,0xaf,0x9e,0x87,0x6b,0x50, + 0x48,0x46,0x3e,0x33,0x2b,0x25,0x2a,0x41,0x51,0x5b,0x5f,0x5d,0x58,0x5b,0x5d,0x5d, + 0x5b,0x55,0x4d,0x44,0x3b,0x38,0x34,0x33,0x35,0x38,0x3c,0x41,0x49,0x4a,0x4a,0x48, + 0x49,0x4a,0x4b,0x48,0x40,0x3c,0x37,0x35,0x30,0x2d,0x2b,0x29,0x25,0x2a,0x35,0x3b, + 0x39,0x37,0x33,0x2d,0x28,0x27,0x2a,0x2a,0x28,0x29,0x2c,0x2f,0x32,0x36,0x35,0x33, + 0x8f,0x7e,0x77,0x87,0x96,0x9a,0x96,0x96,0x99,0x99,0x99,0x9b,0x9e,0xa0,0xa1,0xa0, + 0x9d,0x9a,0x99,0x9b,0x9e,0x9e,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0, + 0xa1,0xa1,0xa1,0xa2,0xa4,0xa4,0xa6,0xa6,0xa9,0xab,0xad,0xae,0xaf,0xb0,0xb3,0xb3, + 0xb4,0xaf,0xa4,0x96,0x8c,0x89,0x92,0xa6,0xbb,0xc9,0xcb,0xbf,0xac,0x95,0x72,0x53, + 0x4b,0x47,0x3f,0x34,0x2f,0x29,0x2e,0x46,0x57,0x5e,0x62,0x5e,0x57,0x5b,0x5d,0x5f, + 0x5d,0x57,0x4e,0x47,0x3d,0x38,0x33,0x33,0x37,0x39,0x3e,0x42,0x48,0x47,0x48,0x48, + 0x49,0x47,0x49,0x46,0x41,0x3f,0x3e,0x37,0x33,0x33,0x33,0x32,0x30,0x35,0x3d,0x47, + 0x44,0x41,0x3e,0x39,0x37,0x34,0x32,0x32,0x31,0x2e,0x2c,0x2e,0x32,0x35,0x35,0x31, + 0x82,0x77,0x7a,0x8b,0x96,0x96,0x94,0x94,0x96,0x96,0x98,0x98,0x9b,0x9f,0xa1,0xa0, + 0x9e,0x9c,0x99,0x97,0x9c,0x9d,0x9e,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0x9f,0xa0, + 0xa1,0xa1,0xa1,0xa2,0xa4,0xa4,0xa6,0xa6,0xa9,0xab,0xad,0xae,0xaf,0xb0,0xb3,0xb3, + 0xb3,0xaf,0xa4,0x97,0x8d,0x8d,0x99,0xab,0xbe,0xc9,0xcc,0xc4,0xb3,0x97,0x76,0x56, + 0x4b,0x49,0x40,0x34,0x2e,0x29,0x30,0x49,0x59,0x61,0x63,0x5f,0x58,0x5b,0x5e,0x5f, + 0x5e,0x59,0x50,0x49,0x3d,0x37,0x34,0x38,0x39,0x3b,0x3d,0x40,0x42,0x42,0x42,0x42, + 0x43,0x42,0x43,0x43,0x42,0x41,0x41,0x3c,0x37,0x36,0x36,0x34,0x37,0x3b,0x43,0x48, + 0x48,0x49,0x48,0x44,0x42,0x41,0x3c,0x3b,0x3c,0x36,0x31,0x32,0x33,0x34,0x33,0x2e, + 0x7d,0x7a,0x7b,0x87,0x88,0x88,0x8a,0x8f,0x93,0x96,0x97,0x96,0x98,0x9c,0xa0,0x9f, + 0x9f,0x9d,0x9a,0x98,0x99,0x9c,0x9e,0x9e,0x9e,0x9e,0x9e,0x9e,0x9f,0x9f,0x9f,0xa0, + 0xa1,0xa1,0xa1,0xa2,0xa4,0xa4,0xa6,0xa6,0xa9,0xaa,0xac,0xae,0xaf,0xb0,0xb3,0xb3, + 0xb3,0xaf,0xa5,0x9a,0x94,0x98,0xa2,0xb0,0xbe,0xc6,0xc9,0xc2,0xaf,0x95,0x71,0x55, + 0x49,0x44,0x3c,0x33,0x2f,0x2a,0x32,0x4d,0x5c,0x64,0x64,0x5f,0x59,0x5c,0x5e,0x5f, + 0x5e,0x59,0x52,0x4b,0x40,0x3a,0x3a,0x39,0x38,0x39,0x3b,0x3e,0x41,0x40,0x3d,0x3f, + 0x3c,0x3f,0x40,0x42,0x45,0x46,0x45,0x41,0x3c,0x3a,0x38,0x38,0x3a,0x3d,0x45,0x48, + 0x4c,0x4f,0x50,0x4e,0x4d,0x4d,0x47,0x46,0x49,0x42,0x3e,0x39,0x3a,0x3a,0x38,0x33, + 0x7c,0x81,0x7e,0x7f,0x7b,0x7e,0x80,0x89,0x90,0x94,0x95,0x95,0x97,0x99,0x9b,0x9f, + 0xa1,0x9d,0x99,0x98,0x96,0x99,0x9a,0x9c,0x9d,0x9d,0x9d,0x9d,0x9e,0x9f,0x9f,0x9f, + 0x9f,0xa1,0xa2,0xa2,0xa3,0xa4,0xa5,0xa7,0xa8,0xa9,0xac,0xad,0xaf,0xb0,0xb2,0xb2, + 0xb3,0xaf,0xa4,0x9c,0x9a,0x9f,0xa8,0xb3,0xbc,0xc4,0xc7,0xbd,0xab,0x8e,0x6a,0x49, + 0x42,0x3e,0x3a,0x36,0x30,0x2b,0x34,0x4e,0x5f,0x65,0x65,0x5e,0x59,0x5c,0x5e,0x5f, + 0x5f,0x5d,0x56,0x4d,0x45,0x40,0x41,0x3f,0x3e,0x3e,0x3f,0x40,0x42,0x44,0x40,0x39, + 0x35,0x37,0x3b,0x41,0x46,0x45,0x43,0x3d,0x3b,0x38,0x34,0x33,0x38,0x40,0x44,0x48, + 0x4d,0x50,0x51,0x4e,0x4f,0x4f,0x4c,0x4c,0x4b,0x44,0x3f,0x3c,0x3b,0x3a,0x39,0x37, + 0x81,0x85,0x84,0x7e,0x79,0x79,0x7c,0x83,0x88,0x8d,0x91,0x92,0x97,0x99,0x9a,0x9d, + 0xa0,0x9f,0x9c,0x9a,0x98,0x98,0x99,0x9c,0x9e,0x9d,0x9d,0x9d,0x9f,0x9f,0x9f,0x9f, + 0x9f,0xa0,0xa1,0xa2,0xa2,0xa3,0xa5,0xa6,0xa7,0xa8,0xaa,0xac,0xae,0xaf,0xb2,0xb3, + 0xb3,0xaf,0xaa,0xa6,0xa1,0xa4,0xab,0xb6,0xc0,0xc8,0xc7,0xbc,0xa6,0x85,0x5e,0x41, + 0x3f,0x3e,0x3e,0x38,0x30,0x2b,0x38,0x52,0x62,0x68,0x67,0x5e,0x59,0x5c,0x5e,0x60, + 0x5f,0x5d,0x57,0x4e,0x47,0x45,0x45,0x43,0x42,0x43,0x45,0x42,0x41,0x42,0x41,0x3a, + 0x32,0x32,0x36,0x39,0x3f,0x3e,0x3d,0x39,0x37,0x35,0x2f,0x2f,0x35,0x3b,0x42,0x4a, + 0x4e,0x50,0x50,0x50,0x50,0x4f,0x49,0x49,0x49,0x43,0x3e,0x3d,0x3b,0x37,0x39,0x3b, + 0x84,0x90,0x8e,0x85,0x7f,0x7d,0x80,0x82,0x84,0x89,0x8b,0x8e,0x94,0x97,0x99,0x9c, + 0x9f,0x9f,0x9f,0x9c,0x99,0x97,0x97,0x99,0x9d,0x9f,0x9d,0x9d,0x9d,0x9d,0x9e,0x9f, + 0x9f,0x9f,0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa7,0xa8,0xa9,0xac,0xad,0xaf,0xb1,0xb0, + 0xb2,0xb1,0xb2,0xae,0xa8,0xa6,0xad,0xb9,0xc3,0xce,0xc9,0xb9,0xa0,0x79,0x51,0x3d, + 0x3e,0x3d,0x3e,0x37,0x30,0x2d,0x3e,0x57,0x66,0x6b,0x68,0x5e,0x57,0x5c,0x5f,0x5f, + 0x60,0x5b,0x53,0x4c,0x43,0x40,0x41,0x43,0x41,0x3f,0x42,0x42,0x3f,0x3d,0x3b,0x35, + 0x2d,0x2b,0x2f,0x31,0x34,0x35,0x36,0x34,0x2e,0x2d,0x28,0x27,0x2e,0x34,0x3b,0x43, + 0x4b,0x50,0x4f,0x51,0x52,0x4f,0x4a,0x46,0x45,0x41,0x3e,0x3b,0x3c,0x37,0x37,0x3c, + 0x99,0x9b,0x99,0x90,0x8a,0x86,0x83,0x83,0x85,0x88,0x8b,0x8e,0x90,0x91,0x97,0x9d, + 0x9f,0xa0,0x9e,0x9c,0x9c,0x9b,0x98,0x98,0x9c,0x9d,0x9f,0x9f,0x9d,0x9d,0x9e,0x9f, + 0x9f,0x9f,0xa0,0xa1,0xa2,0xa3,0xa4,0xa4,0xa7,0xa7,0xa9,0xab,0xad,0xae,0xb0,0xb2, + 0xb6,0xbb,0xbb,0xb5,0xaf,0xac,0xb1,0xbe,0xcb,0xd2,0xcb,0xb8,0x98,0x6e,0x46,0x3d, + 0x3c,0x3b,0x3a,0x34,0x30,0x2f,0x42,0x5c,0x6a,0x71,0x6b,0x5f,0x58,0x5c,0x5f,0x5f, + 0x5f,0x58,0x4e,0x46,0x3e,0x3c,0x3f,0x41,0x40,0x3e,0x3d,0x40,0x3c,0x39,0x35,0x30, + 0x29,0x28,0x29,0x29,0x2a,0x2c,0x2f,0x2a,0x26,0x25,0x24,0x23,0x2a,0x34,0x3a,0x3f, + 0x47,0x4e,0x50,0x53,0x53,0x50,0x4b,0x47,0x42,0x3d,0x3a,0x39,0x3a,0x37,0x39,0x3b, + 0x9d,0xa3,0xa3,0x98,0x8c,0x89,0x8a,0x8b,0x88,0x88,0x8a,0x8d,0x8d,0x8b,0x8e,0x92, + 0x99,0x9c,0x9a,0x9c,0x9c,0x9a,0x93,0x93,0x95,0x9a,0x9e,0x9f,0x9d,0x9e,0x9e,0x9f, + 0x9f,0x9f,0xa0,0xa1,0xa2,0xa3,0xa4,0xa4,0xa7,0xa7,0xa8,0xab,0xac,0xae,0xb2,0xb8, + 0xbe,0xc0,0xbe,0xba,0xb0,0xac,0xb4,0xc1,0xce,0xd1,0xca,0xb3,0x91,0x61,0x41,0x3e, + 0x3f,0x3d,0x3a,0x32,0x2c,0x2b,0x43,0x61,0x70,0x76,0x6e,0x60,0x5a,0x5c,0x5f,0x60, + 0x5f,0x55,0x49,0x3d,0x37,0x36,0x39,0x41,0x41,0x3c,0x35,0x35,0x33,0x32,0x2e,0x2b, + 0x26,0x23,0x26,0x25,0x27,0x26,0x26,0x25,0x21,0x23,0x22,0x22,0x2b,0x35,0x3a,0x42, + 0x49,0x4f,0x51,0x55,0x52,0x4e,0x4f,0x46,0x40,0x39,0x37,0x37,0x36,0x3a,0x3b,0x39, + 0x9f,0xaa,0xa9,0x9c,0x91,0x8b,0x8b,0x8d,0x8d,0x8c,0x8c,0x8c,0x87,0x81,0x7f,0x7f, + 0x85,0x89,0x8e,0x91,0x8d,0x84,0x7a,0x78,0x83,0x91,0x9d,0x9e,0x9e,0x9e,0x9e,0x9e, + 0x9e,0x9f,0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa5,0xa6,0xa8,0xab,0xae,0xb3,0xbb,0xc0, + 0xc4,0xc6,0xc2,0xbb,0xb4,0xb0,0xb7,0xc6,0xd2,0xd0,0xc4,0xa8,0x83,0x55,0x43,0x46, + 0x3f,0x3d,0x39,0x30,0x27,0x27,0x45,0x67,0x77,0x7a,0x73,0x62,0x59,0x5c,0x5f,0x60, + 0x5c,0x50,0x42,0x35,0x2f,0x32,0x36,0x39,0x3c,0x39,0x31,0x30,0x2f,0x2d,0x28,0x23, + 0x21,0x20,0x20,0x23,0x23,0x22,0x21,0x21,0x23,0x24,0x25,0x27,0x2c,0x39,0x41,0x45, + 0x49,0x4e,0x50,0x52,0x51,0x4e,0x4d,0x47,0x40,0x39,0x38,0x37,0x37,0x3a,0x3a,0x38, + 0x98,0xa9,0xaa,0xa1,0x95,0x8d,0x8a,0x8a,0x8d,0x8e,0x8d,0x8a,0x83,0x76,0x6a,0x65, + 0x6c,0x74,0x77,0x75,0x6f,0x65,0x5d,0x5c,0x6e,0x86,0x98,0x9c,0x9c,0x9e,0x9e,0x9e, + 0x9e,0x9f,0xa0,0xa1,0xa2,0xa3,0xa3,0xa4,0xa5,0xa5,0xa8,0xad,0xb4,0xbc,0xc3,0xc6, + 0xc8,0xc8,0xc2,0xbc,0xb7,0xb5,0xbe,0xcc,0xd0,0xc9,0xbc,0x9c,0x73,0x4e,0x4c,0x4b, + 0x43,0x3b,0x34,0x29,0x1f,0x25,0x45,0x6b,0x7a,0x7d,0x75,0x64,0x58,0x5c,0x5f,0x5d, + 0x5a,0x4d,0x3d,0x30,0x2a,0x2e,0x32,0x38,0x3b,0x38,0x31,0x2d,0x2a,0x26,0x22,0x1f, + 0x20,0x1f,0x1e,0x20,0x20,0x20,0x1f,0x1f,0x22,0x23,0x26,0x27,0x2d,0x3b,0x40,0x47, + 0x4b,0x4e,0x4e,0x4f,0x4e,0x4d,0x4b,0x45,0x3f,0x39,0x36,0x37,0x39,0x37,0x39,0x37, + 0x92,0xa6,0xad,0xa5,0x9c,0x92,0x8e,0x8c,0x8d,0x8c,0x8c,0x8a,0x80,0x70,0x61,0x55, + 0x56,0x5a,0x5d,0x5d,0x56,0x49,0x41,0x43,0x64,0x84,0x97,0x9d,0x9c,0x9d,0x9e,0x9e, + 0x9e,0x9f,0xa0,0xa2,0xa3,0xa3,0xa3,0xa5,0xa7,0xac,0xb2,0xba,0xbf,0xc4,0xc7,0xca, + 0xc8,0xc8,0xc4,0xbd,0xb9,0xbc,0xc8,0xd1,0xce,0xc3,0xae,0x8c,0x60,0x4d,0x51,0x4e, + 0x45,0x39,0x2e,0x26,0x1d,0x25,0x46,0x6f,0x7d,0x7f,0x79,0x66,0x58,0x5d,0x5f,0x5f, + 0x57,0x48,0x39,0x2d,0x26,0x2e,0x39,0x3d,0x3e,0x3c,0x36,0x30,0x2a,0x28,0x25,0x23, + 0x21,0x1f,0x1e,0x1e,0x1e,0x1e,0x1c,0x1f,0x21,0x22,0x27,0x2c,0x36,0x3e,0x45,0x49, + 0x4c,0x4e,0x4c,0x4b,0x4a,0x49,0x43,0x3f,0x3c,0x39,0x34,0x35,0x37,0x3b,0x3b,0x3c, + 0x95,0xa8,0xaf,0xa7,0x9d,0x98,0x8d,0x8f,0x8c,0x8b,0x8b,0x8c,0x7f,0x70,0x61,0x58, + 0x52,0x51,0x53,0x52,0x50,0x4c,0x47,0x47,0x73,0x91,0x9f,0x9f,0x9e,0x9c,0x9d,0x9e, + 0x9e,0x9f,0xa1,0xa1,0xa3,0xa4,0xa8,0xaa,0xb2,0xbb,0xc0,0xc6,0xc8,0xc9,0xc9,0xc8, + 0xc8,0xc8,0xc4,0xc0,0xbf,0xc6,0xd1,0xd4,0xc9,0xbc,0x9e,0x78,0x56,0x51,0x53,0x4e, + 0x45,0x3a,0x2e,0x21,0x1a,0x22,0x47,0x72,0x81,0x83,0x7b,0x67,0x59,0x5d,0x5e,0x5d, + 0x54,0x44,0x39,0x2e,0x29,0x32,0x3b,0x3e,0x3e,0x3c,0x38,0x34,0x30,0x2c,0x28,0x27, + 0x24,0x1f,0x1d,0x1d,0x1e,0x1d,0x1d,0x1d,0x21,0x22,0x28,0x2f,0x38,0x3f,0x47,0x4b, + 0x4c,0x4c,0x4a,0x49,0x44,0x41,0x3b,0x39,0x38,0x3a,0x38,0x36,0x38,0x3c,0x40,0x41, + 0x95,0xa7,0xab,0xa8,0xa1,0x9c,0x92,0x8f,0x8c,0x89,0x8a,0x89,0x80,0x71,0x5e,0x58, + 0x54,0x52,0x52,0x52,0x51,0x4d,0x4d,0x5f,0x8c,0xa4,0xab,0xa4,0x9e,0x9c,0x9c,0x9d, + 0x9e,0x9e,0xa0,0xa0,0x9f,0xa7,0xb1,0xb8,0xbf,0xc7,0xcc,0xcf,0xce,0xcc,0xc9,0xc6, + 0xc6,0xc9,0xc7,0xc4,0xc4,0xcf,0xd9,0xd8,0xcb,0xb2,0x8b,0x60,0x53,0x52,0x52,0x4d, + 0x46,0x3c,0x2c,0x22,0x1e,0x25,0x48,0x74,0x83,0x85,0x7d,0x6b,0x5b,0x5d,0x5f,0x5b, + 0x50,0x42,0x36,0x2d,0x2b,0x31,0x3a,0x3f,0x3d,0x39,0x38,0x34,0x32,0x30,0x2e,0x2b, + 0x28,0x21,0x1e,0x1d,0x1b,0x19,0x1a,0x19,0x1d,0x22,0x27,0x30,0x37,0x3f,0x47,0x4b, + 0x4d,0x4b,0x4b,0x48,0x44,0x3f,0x3a,0x35,0x36,0x37,0x37,0x38,0x40,0x42,0x45,0x4a, + 0x93,0xa2,0xa6,0xa5,0xa1,0x9c,0x99,0x90,0x8c,0x89,0x88,0x88,0x80,0x73,0x65,0x59, + 0x56,0x53,0x52,0x52,0x52,0x4e,0x4c,0x77,0xa3,0xb5,0xb4,0xa8,0xa0,0x9c,0x9c,0x9c, + 0x9e,0x9d,0x9f,0xa0,0xa3,0xb0,0xbb,0xc6,0xcc,0xce,0xd2,0xd3,0xcf,0xcd,0xc8,0xc4, + 0xc6,0xc8,0xc8,0xc6,0xcb,0xd8,0xdb,0xdb,0xc9,0xad,0x7e,0x55,0x52,0x52,0x50,0x4a, + 0x47,0x3f,0x2d,0x25,0x21,0x26,0x4c,0x72,0x84,0x86,0x7d,0x6b,0x5d,0x5d,0x5e,0x58, + 0x4c,0x3e,0x33,0x2b,0x2f,0x35,0x3b,0x3e,0x3e,0x3b,0x34,0x32,0x30,0x32,0x2f,0x2b, + 0x29,0x25,0x20,0x1f,0x1c,0x1d,0x1c,0x1b,0x1e,0x23,0x27,0x2f,0x38,0x3e,0x45,0x49, + 0x4a,0x48,0x49,0x48,0x46,0x42,0x3a,0x35,0x33,0x37,0x3a,0x3f,0x45,0x48,0x49,0x4f, + 0xb8,0x9c,0xa1,0xa3,0xa2,0x9c,0x99,0x94,0x8e,0x8a,0x88,0x84,0x7f,0x75,0x67,0x5c, + 0x55,0x52,0x52,0x51,0x50,0x4c,0x5c,0x96,0xb6,0xc1,0xbb,0xaf,0xa5,0x9d,0x9e,0x9e, + 0xa2,0xa7,0xa9,0xaa,0xb0,0xbc,0xc4,0xcc,0xd0,0xd1,0xd1,0xd2,0xd0,0xcb,0xc6,0xc4, + 0xc6,0xc8,0xc7,0xcb,0xd1,0xdb,0xde,0xd6,0xc0,0xa7,0x7a,0x58,0x50,0x4d,0x48,0x46, + 0x44,0x3b,0x31,0x2b,0x24,0x28,0x4e,0x72,0x83,0x86,0x7d,0x69,0x5d,0x5e,0x5d,0x57, + 0x4c,0x3f,0x34,0x2d,0x31,0x35,0x39,0x3a,0x3b,0x38,0x30,0x2e,0x2f,0x31,0x2f,0x2a, + 0x28,0x25,0x1f,0x22,0x21,0x22,0x22,0x22,0x23,0x26,0x2b,0x31,0x3d,0x45,0x46,0x46, + 0x45,0x45,0x47,0x4a,0x4a,0x45,0x3e,0x37,0x37,0x3a,0x3f,0x45,0x4b,0x4c,0x4f,0x55, + 0xdd,0xb9,0xa1,0xa2,0xa1,0x9c,0x99,0x97,0x90,0x8b,0x85,0x84,0x7f,0x77,0x6a,0x5d, + 0x53,0x51,0x51,0x4f,0x4d,0x50,0x80,0xad,0xc2,0xc9,0xc1,0xb6,0xab,0xa3,0xa5,0xad, + 0xb2,0xb6,0xb9,0xba,0xbe,0xc5,0xcb,0xcf,0xcf,0xd1,0xd1,0xd0,0xd0,0xcb,0xc5,0xc5, + 0xc7,0xca,0xca,0xcb,0xd2,0xda,0xda,0xcd,0xb9,0x9e,0x78,0x5c,0x51,0x4c,0x45,0x46, + 0x45,0x3d,0x36,0x30,0x28,0x2c,0x50,0x70,0x83,0x85,0x7b,0x68,0x5d,0x5d,0x5c,0x56, + 0x4b,0x3f,0x34,0x2e,0x32,0x36,0x38,0x37,0x34,0x32,0x32,0x30,0x30,0x30,0x2c,0x28, + 0x25,0x25,0x23,0x22,0x25,0x23,0x24,0x26,0x28,0x2a,0x2c,0x32,0x3d,0x43,0x44,0x44, + 0x42,0x42,0x45,0x4b,0x4d,0x49,0x44,0x3b,0x3c,0x3e,0x45,0x4a,0x4d,0x4e,0x52,0x56, + 0xe1,0xdb,0xb6,0xa1,0xa2,0x9e,0x99,0x97,0x92,0x8e,0x86,0x85,0x81,0x79,0x6c,0x60, + 0x55,0x50,0x50,0x4f,0x4e,0x6d,0xa0,0xbd,0xca,0xcb,0xc4,0xba,0xb5,0xb3,0xb6,0xbd, + 0xc2,0xc4,0xc5,0xc6,0xc7,0xca,0xcc,0xcf,0xd0,0xd0,0xd0,0xd0,0xcf,0xca,0xc7,0xc8, + 0xca,0xc9,0xc7,0xc5,0xc6,0xcc,0xce,0xc2,0xb2,0x99,0x78,0x63,0x54,0x4b,0x47,0x49, + 0x47,0x3e,0x38,0x31,0x2a,0x2d,0x51,0x70,0x81,0x83,0x7a,0x67,0x5d,0x5c,0x5b,0x54, + 0x49,0x3d,0x33,0x2d,0x31,0x36,0x39,0x3a,0x36,0x35,0x38,0x34,0x31,0x2f,0x29,0x25, + 0x22,0x20,0x1e,0x21,0x25,0x24,0x25,0x28,0x2b,0x2e,0x32,0x36,0x3d,0x41,0x42,0x41, + 0x3e,0x3c,0x40,0x47,0x4a,0x4b,0x47,0x42,0x43,0x42,0x47,0x4e,0x50,0x51,0x53,0x56, + 0xde,0xe3,0xda,0xb5,0xa4,0xa1,0x9c,0x99,0x94,0x91,0x8a,0x87,0x82,0x7b,0x6c,0x5f, + 0x56,0x51,0x4f,0x4d,0x5d,0x91,0xb4,0xc9,0xcd,0xcb,0xc6,0xbf,0xbd,0xc0,0xc4,0xc9, + 0xcb,0xcc,0xcb,0xcb,0xcc,0xcd,0xcf,0xd0,0xd0,0xd0,0xd0,0xd1,0xcd,0xcb,0xca,0xc9, + 0xc9,0xc3,0xbc,0xb6,0xb6,0xb6,0xb9,0xb0,0xa1,0x92,0x81,0x6c,0x5c,0x4f,0x48,0x46, + 0x44,0x3c,0x36,0x2f,0x28,0x2c,0x52,0x70,0x81,0x84,0x7a,0x67,0x5d,0x5a,0x58,0x51, + 0x47,0x3c,0x31,0x2f,0x32,0x33,0x37,0x3c,0x39,0x37,0x37,0x33,0x2e,0x2a,0x24,0x1e, + 0x1c,0x1a,0x1a,0x1d,0x21,0x23,0x26,0x2c,0x34,0x36,0x39,0x3b,0x42,0x42,0x41,0x3d, + 0x3a,0x37,0x39,0x43,0x49,0x4d,0x49,0x48,0x48,0x49,0x4a,0x50,0x53,0x53,0x51,0x53, + 0xdc,0xde,0xe5,0xe0,0xb6,0xa4,0x9f,0x9d,0x99,0x95,0x8e,0x8a,0x83,0x7b,0x69,0x5d, + 0x55,0x4e,0x4d,0x58,0x85,0xad,0xc3,0xcd,0xce,0xcb,0xc6,0xc2,0xc2,0xc6,0xc5,0xc3, + 0xc1,0xc6,0xca,0xcb,0xcd,0xce,0xcf,0xd0,0xd1,0xd0,0xd0,0xd0,0xcf,0xcc,0xcd,0xca, + 0xc5,0xb7,0xab,0xa4,0xa2,0xa3,0xa7,0x9e,0x9a,0x90,0x86,0x72,0x66,0x58,0x49,0x43, + 0x3e,0x37,0x2f,0x2a,0x22,0x2b,0x51,0x73,0x82,0x84,0x7a,0x67,0x5d,0x5a,0x56,0x4e, + 0x45,0x38,0x31,0x30,0x30,0x30,0x32,0x36,0x38,0x37,0x34,0x30,0x2b,0x27,0x20,0x1b, + 0x17,0x17,0x17,0x1a,0x1d,0x22,0x27,0x31,0x3a,0x40,0x3f,0x3d,0x42,0x40,0x3c,0x37, + 0x34,0x34,0x39,0x3f,0x4a,0x4e,0x4c,0x49,0x4b,0x4f,0x4d,0x51,0x55,0x51,0x51,0x53, + 0xdf,0xdd,0xdf,0xe4,0xdf,0xbb,0xa5,0xa0,0x9b,0x98,0x93,0x8f,0x84,0x76,0x64,0x59, + 0x51,0x4c,0x50,0x7a,0xa5,0xbd,0xcb,0xcd,0xcd,0xca,0xc6,0xc5,0xc3,0xc4,0xbc,0xaf, + 0xa7,0xb3,0xbe,0xc8,0xcd,0xcd,0xce,0xcf,0xd0,0xcf,0xcf,0xd1,0xd0,0xcd,0xcb,0xc6, + 0xbb,0xa9,0x96,0x8b,0x8d,0x96,0x9c,0x97,0x96,0x96,0x8b,0x79,0x70,0x60,0x48,0x41, + 0x3a,0x31,0x28,0x21,0x1d,0x27,0x54,0x72,0x81,0x82,0x78,0x66,0x5e,0x5a,0x52,0x49, + 0x3f,0x34,0x30,0x2e,0x2c,0x2c,0x2e,0x31,0x33,0x31,0x2e,0x2e,0x28,0x22,0x1c,0x16, + 0x15,0x16,0x17,0x18,0x1a,0x1e,0x26,0x32,0x3e,0x44,0x44,0x42,0x41,0x3f,0x3b,0x35, + 0x32,0x35,0x3c,0x40,0x48,0x4e,0x4f,0x4a,0x4a,0x4f,0x4c,0x4d,0x50,0x54,0x53,0x52, + 0xdf,0xdf,0xdc,0xe0,0xe4,0xde,0xb7,0xa5,0x9e,0x9c,0x97,0x91,0x85,0x71,0x5f,0x51, + 0x4c,0x4f,0x72,0x9b,0xb7,0xc8,0xcb,0xcb,0xcd,0xc9,0xc5,0xc4,0xc3,0xbe,0xad,0x90, + 0x82,0x97,0xb3,0xc5,0xce,0xce,0xcd,0xcd,0xce,0xcf,0xcf,0xd0,0xcf,0xcc,0xca,0xbf, + 0xb1,0xa1,0x8d,0x86,0x88,0x93,0x98,0x99,0x9c,0x9c,0x8f,0x83,0x78,0x65,0x4a,0x3c, + 0x35,0x29,0x1e,0x18,0x18,0x29,0x52,0x72,0x80,0x80,0x77,0x65,0x5d,0x59,0x4f,0x45, + 0x3b,0x31,0x2f,0x2b,0x29,0x29,0x29,0x29,0x2a,0x2a,0x27,0x23,0x1f,0x1c,0x17,0x14, + 0x14,0x16,0x17,0x15,0x19,0x1d,0x26,0x31,0x3c,0x42,0x46,0x46,0x45,0x41,0x3a,0x35, + 0x34,0x38,0x40,0x44,0x48,0x4d,0x4f,0x49,0x49,0x49,0x4b,0x4a,0x4e,0x56,0x56,0x53, + 0xdf,0xdf,0xdd,0xe0,0xe0,0xe4,0xdf,0xba,0xa4,0x9f,0x9a,0x96,0x84,0x6c,0x57,0x50, + 0x4c,0x69,0x96,0xb4,0xc3,0xc9,0xcb,0xcc,0xcc,0xc8,0xc6,0xc3,0xc5,0xbc,0x98,0x6a, + 0x54,0x7c,0xa8,0xc0,0xcb,0xce,0xcc,0xcc,0xcb,0xcd,0xcf,0xcf,0xd0,0xcb,0xc3,0xb8, + 0xaa,0xa1,0x96,0x92,0x93,0x96,0x97,0x98,0x9a,0x9d,0x9a,0x8e,0x82,0x6e,0x53,0x40, + 0x37,0x2d,0x26,0x24,0x24,0x32,0x58,0x73,0x7f,0x7f,0x76,0x64,0x5b,0x55,0x4c,0x41, + 0x38,0x32,0x2f,0x2b,0x28,0x25,0x22,0x24,0x22,0x22,0x1e,0x1c,0x17,0x16,0x13,0x12, + 0x13,0x16,0x17,0x1a,0x1c,0x20,0x25,0x2e,0x38,0x40,0x46,0x49,0x46,0x40,0x37,0x34, + 0x37,0x3c,0x43,0x49,0x4e,0x4f,0x4e,0x48,0x44,0x45,0x47,0x46,0x4b,0x54,0x53,0x4f, + 0xdd,0xe0,0xdf,0xdf,0xe0,0xe2,0xe5,0xe0,0xb7,0xa1,0x9c,0x97,0x81,0x68,0x53,0x4b, + 0x5e,0x8c,0xad,0xc1,0xc7,0xc8,0xcb,0xcd,0xca,0xc9,0xc7,0xc3,0xc3,0xb2,0x92,0x63, + 0x4d,0x6d,0x9d,0xb9,0xc8,0xcd,0xcb,0xca,0xca,0xcc,0xce,0xcf,0xce,0xc7,0xbf,0xb4, + 0xa8,0xa2,0x9e,0x9b,0x99,0x97,0x97,0x9a,0x9d,0x9b,0x9a,0x90,0x83,0x73,0x5d,0x49, + 0x41,0x3c,0x37,0x36,0x36,0x40,0x61,0x77,0x7f,0x7f,0x74,0x62,0x5a,0x4e,0x45,0x3d, + 0x38,0x30,0x2d,0x2b,0x28,0x24,0x1e,0x1e,0x1c,0x1a,0x17,0x16,0x12,0x12,0x11,0x11, + 0x14,0x16,0x19,0x1b,0x1e,0x21,0x25,0x2a,0x34,0x3b,0x3e,0x41,0x41,0x3c,0x35,0x33, + 0x36,0x3c,0x45,0x4b,0x4f,0x4e,0x48,0x44,0x3e,0x3d,0x3f,0x40,0x47,0x4e,0x52,0x4f, + 0xdf,0xde,0xe2,0xdf,0xdf,0xdf,0xe4,0xe5,0xe0,0xb3,0x9d,0x94,0x7c,0x60,0x4f,0x59, + 0x85,0xa9,0xbf,0xc9,0xc6,0xc7,0xcb,0xcf,0xcc,0xc9,0xc7,0xc4,0xc2,0xb0,0x90,0x64, + 0x4d,0x62,0x8e,0xb0,0xc4,0xcd,0xcb,0xca,0xca,0xcc,0xce,0xcf,0xcc,0xc3,0xb8,0xb2, + 0xaa,0xa9,0xa7,0xa5,0xa1,0x9a,0x97,0x97,0x96,0x96,0x97,0x8d,0x81,0x75,0x65,0x56, + 0x51,0x4d,0x4c,0x4b,0x4a,0x4f,0x6c,0x7b,0x7f,0x7f,0x75,0x61,0x57,0x4a,0x42,0x38, + 0x30,0x2d,0x2c,0x2a,0x27,0x23,0x20,0x1b,0x1c,0x18,0x14,0x13,0x12,0x10,0x10,0x12, + 0x13,0x14,0x18,0x1b,0x1d,0x1f,0x21,0x25,0x2e,0x35,0x38,0x39,0x3b,0x3a,0x35,0x31, + 0x35,0x3b,0x44,0x4a,0x4e,0x4b,0x42,0x3d,0x39,0x36,0x38,0x3e,0x45,0x4c,0x51,0x50, + 0xdf,0xe1,0xe1,0xe0,0xe2,0xe5,0xe5,0xe5,0xe6,0xdf,0xae,0x8b,0x70,0x57,0x53,0x7d, + 0xa6,0xbb,0xc5,0xc9,0xc6,0xc7,0xca,0xcd,0xcd,0xc9,0xc7,0xc4,0xbf,0xb3,0x9b,0x7b, + 0x62,0x59,0x80,0xa4,0xbc,0xca,0xcd,0xcb,0xcd,0xce,0xcf,0xcc,0xc6,0xbc,0xb3,0xac, + 0xad,0xae,0xab,0xab,0xa7,0x9e,0x98,0x93,0x91,0x91,0x92,0x89,0x7e,0x75,0x67,0x61, + 0x5f,0x5b,0x59,0x59,0x55,0x58,0x70,0x7d,0x7f,0x7c,0x71,0x5e,0x51,0x47,0x3d,0x2f, + 0x27,0x2a,0x2a,0x28,0x25,0x20,0x1c,0x1a,0x19,0x16,0x13,0x11,0x11,0x10,0x0f,0x11, + 0x12,0x12,0x16,0x19,0x1c,0x1f,0x20,0x25,0x29,0x2c,0x30,0x32,0x34,0x35,0x32,0x2d, + 0x2e,0x36,0x3f,0x44,0x48,0x44,0x3d,0x37,0x33,0x33,0x38,0x3e,0x44,0x4c,0x50,0x50, + 0xdd,0xe3,0xe0,0xe5,0xde,0xde,0xe2,0xe5,0xe7,0xec,0xdd,0x8e,0x62,0x52,0x74,0xa0, + 0xba,0xc5,0xc5,0xc9,0xc7,0xc6,0xc8,0xcc,0xcf,0xca,0xc7,0xc4,0xc1,0xbe,0xac,0x98, + 0x7e,0x68,0x73,0x98,0xae,0xbf,0xc7,0xcf,0xd0,0xd0,0xcc,0xc8,0xbf,0xb5,0xad,0xa7, + 0xab,0xad,0xaf,0xae,0xad,0xa5,0x9c,0x93,0x8d,0x89,0x88,0x84,0x7c,0x6f,0x6a,0x68, + 0x69,0x67,0x66,0x64,0x5e,0x5e,0x73,0x7c,0x7c,0x7a,0x70,0x5d,0x4d,0x42,0x36,0x29, + 0x24,0x26,0x26,0x25,0x22,0x20,0x1e,0x19,0x18,0x15,0x13,0x11,0x10,0x10,0x10,0x11, + 0x12,0x13,0x17,0x1a,0x1c,0x1e,0x20,0x26,0x29,0x2a,0x2e,0x32,0x2f,0x2e,0x2c,0x2a, + 0x2a,0x31,0x39,0x3b,0x3f,0x3c,0x37,0x32,0x34,0x38,0x3c,0x3f,0x45,0x4c,0x50,0x50, + 0xde,0xdd,0xdf,0xdc,0xdb,0xe0,0xe5,0xe5,0xe5,0xe9,0xeb,0xd5,0x6d,0x6c,0x99,0xb6, + 0xc6,0xc8,0xc6,0xc7,0xc8,0xc6,0xc7,0xc9,0xcc,0xcb,0xc6,0xc3,0xbf,0xc1,0xb8,0xaa, + 0x95,0x7e,0x72,0x82,0x9b,0xae,0xbd,0xca,0xd0,0xcf,0xc9,0xbf,0xb6,0xac,0xa7,0xa6, + 0xa7,0xaa,0xad,0xae,0xaf,0xa8,0xa2,0x98,0x8c,0x86,0x85,0x83,0x82,0x78,0x6d,0x66, + 0x66,0x66,0x64,0x64,0x5f,0x5d,0x6e,0x77,0x7b,0x78,0x6e,0x58,0x4a,0x3e,0x30,0x26, + 0x1f,0x24,0x24,0x21,0x23,0x22,0x1e,0x1b,0x18,0x14,0x12,0x0f,0x12,0x12,0x12,0x11, + 0x10,0x15,0x18,0x1b,0x1c,0x1d,0x1d,0x27,0x2a,0x2d,0x30,0x31,0x2e,0x2a,0x28,0x28, + 0x2c,0x2e,0x34,0x33,0x36,0x36,0x31,0x31,0x33,0x3c,0x40,0x42,0x47,0x4b,0x50,0x4e, + 0xdf,0xde,0xe1,0xda,0xda,0xe2,0xe6,0xe8,0xe8,0xe9,0xea,0xe9,0xce,0x98,0xb4,0xc5, + 0xc9,0xc6,0xc5,0xc6,0xc9,0xc7,0xc7,0xc9,0xcb,0xcb,0xc6,0xc3,0xbf,0xbe,0xbf,0xb8, + 0xa9,0x94,0x85,0x7b,0x84,0x96,0xad,0xc1,0xcc,0xc9,0xc1,0xb5,0xac,0xa6,0xa5,0xa1, + 0xa3,0xa6,0xab,0xb0,0xb2,0xaf,0xa8,0x9b,0x92,0x8c,0x8e,0x90,0x92,0x8c,0x7c,0x6d, + 0x63,0x61,0x61,0x60,0x5b,0x59,0x6b,0x72,0x76,0x72,0x69,0x55,0x49,0x3a,0x2d,0x20, + 0x1d,0x21,0x21,0x23,0x28,0x25,0x1f,0x1a,0x17,0x14,0x11,0x10,0x11,0x13,0x14,0x12, + 0x13,0x17,0x1c,0x1b,0x1b,0x1c,0x1d,0x25,0x2c,0x30,0x31,0x32,0x2e,0x2a,0x29,0x2c, + 0x2d,0x30,0x34,0x36,0x34,0x34,0x30,0x32,0x36,0x3f,0x45,0x45,0x47,0x4a,0x4f,0x4c, + 0xdf,0xe1,0xde,0xd8,0xd9,0xe1,0xea,0xee,0xec,0xea,0xed,0xeb,0xea,0xd5,0xc4,0xc8, + 0xc7,0xc7,0xc7,0xc7,0xca,0xc8,0xc6,0xc9,0xca,0xcb,0xc7,0xc3,0xc0,0xbf,0xc0,0xbd, + 0xb3,0xa2,0x93,0x83,0x70,0x72,0x96,0xb3,0xbd,0xbd,0xb4,0xa9,0xa0,0x9f,0x9f,0x9f, + 0xa0,0xa3,0xaa,0xad,0xb3,0xb1,0xab,0x9c,0x97,0x94,0x98,0x9c,0x9d,0x99,0x87,0x74, + 0x5e,0x54,0x53,0x54,0x50,0x4e,0x60,0x6a,0x6e,0x6b,0x65,0x53,0x43,0x34,0x26,0x1b, + 0x1a,0x1d,0x21,0x25,0x28,0x25,0x1f,0x1a,0x14,0x13,0x10,0x0f,0x0f,0x11,0x12,0x13, + 0x15,0x1b,0x1d,0x1b,0x1b,0x1c,0x1d,0x25,0x2b,0x2f,0x2e,0x2f,0x2e,0x2c,0x2a,0x29, + 0x2c,0x30,0x35,0x39,0x3a,0x37,0x32,0x37,0x39,0x42,0x49,0x47,0x47,0x4a,0x4b,0x4b, + 0xdd,0xdf,0xde,0xdc,0xe0,0xe2,0xeb,0xef,0xed,0xed,0xef,0xe5,0xe9,0xeb,0xd9,0xca, + 0xc9,0xc7,0xc7,0xc7,0xc9,0xc9,0xc6,0xc9,0xc9,0xcb,0xc8,0xc2,0xc0,0xbe,0xbf,0xbb, + 0xb7,0xad,0x9e,0x8b,0x6d,0x5c,0x81,0xa2,0xae,0xb0,0xa5,0x9b,0x9a,0x99,0x99,0x9b, + 0x9d,0xa0,0xa7,0xac,0xb3,0xb4,0xb1,0xa4,0x99,0x95,0x9a,0x9e,0xa0,0x9a,0x8b,0x75, + 0x59,0x46,0x43,0x47,0x43,0x4d,0x5f,0x67,0x66,0x66,0x5f,0x4e,0x3f,0x30,0x23,0x18, + 0x18,0x18,0x20,0x23,0x24,0x22,0x20,0x19,0x14,0x12,0x10,0x10,0x0f,0x11,0x12,0x15, + 0x17,0x1b,0x1e,0x1c,0x1b,0x1b,0x1c,0x22,0x29,0x2f,0x2d,0x2c,0x2d,0x2a,0x2a,0x2c, + 0x2f,0x32,0x37,0x3c,0x3d,0x3d,0x3d,0x43,0x43,0x47,0x4b,0x4a,0x49,0x49,0x48,0x46, + 0xe0,0xe0,0xdd,0xdc,0xe2,0xe7,0xed,0xef,0xf1,0xf0,0xed,0xe1,0xe6,0xe9,0xea,0xd3, + 0xc9,0xc7,0xc8,0xc8,0xc8,0xc9,0xc7,0xc8,0xc8,0xc9,0xc8,0xc0,0xc0,0xbc,0xbb,0xb8, + 0xb2,0xac,0x9f,0x83,0x63,0x59,0x79,0x97,0xa3,0xa2,0x99,0x97,0x96,0x95,0x9a,0x98, + 0x9b,0x9e,0xa5,0xac,0xb2,0xb5,0xb0,0xa7,0x97,0x8b,0x90,0x99,0x9c,0x94,0x83,0x71, + 0x57,0x47,0x49,0x57,0x68,0x7c,0x82,0x7b,0x6f,0x63,0x56,0x49,0x3c,0x2e,0x23,0x19, + 0x17,0x18,0x1d,0x20,0x20,0x1e,0x1c,0x18,0x13,0x0f,0x0f,0x0f,0x0e,0x10,0x11,0x15, + 0x19,0x1c,0x1f,0x1c,0x1b,0x1a,0x1b,0x1f,0x26,0x2d,0x2e,0x2c,0x2c,0x2b,0x2d,0x30, + 0x35,0x38,0x3c,0x40,0x3d,0x3d,0x41,0x46,0x47,0x4b,0x4e,0x4b,0x49,0x49,0x47,0x43, + 0xe0,0xe2,0xdf,0xe1,0xe4,0xe5,0xee,0xf0,0xf0,0xeb,0xf1,0xd5,0xec,0xf0,0xf0,0xea, + 0xd1,0xca,0xcb,0xc9,0xc8,0xc8,0xc8,0xc7,0xc7,0xc6,0xc9,0xc5,0xbf,0xbe,0xb9,0xb5, + 0xad,0x9f,0x92,0x75,0x62,0x6c,0x87,0x93,0x9b,0x99,0x96,0x95,0x94,0x95,0x97,0x98, + 0x97,0x9a,0x9f,0xab,0xb3,0xb6,0xb4,0xab,0x98,0x83,0x7d,0x83,0x8e,0x8e,0x88,0x80, + 0x7f,0x80,0x89,0x99,0xa4,0xac,0xa4,0x97,0x83,0x6a,0x4e,0x42,0x38,0x2a,0x1d,0x17, + 0x15,0x17,0x1a,0x1c,0x1d,0x1b,0x18,0x12,0x0f,0x10,0x0f,0x10,0x0f,0x10,0x11,0x15, + 0x19,0x1a,0x1c,0x1d,0x1b,0x1a,0x18,0x1a,0x23,0x2b,0x2c,0x2c,0x2d,0x2d,0x31,0x37, + 0x3b,0x3c,0x41,0x43,0x40,0x3d,0x41,0x43,0x46,0x4b,0x4d,0x4f,0x4b,0x47,0x46,0x42, + 0xe3,0xe5,0xe6,0xe3,0xe5,0xdd,0xea,0xf0,0xeb,0xec,0xe5,0xd7,0xe5,0xeb,0xec,0xf0, + 0xea,0xcf,0xc9,0xca,0xc8,0xc8,0xc8,0xc8,0xc8,0xc8,0xc7,0xc6,0xbc,0xbd,0xb5,0xac, + 0x9a,0x85,0x78,0x6d,0x70,0x82,0x91,0x96,0x98,0x96,0x95,0x93,0x94,0x96,0x96,0x94, + 0x93,0x95,0x9a,0xa6,0xb2,0xb6,0xb3,0xa6,0x96,0x7d,0x72,0x7a,0x8f,0xa0,0xa7,0xac, + 0xb1,0xb7,0xbd,0xc6,0xcd,0xce,0xc5,0xb1,0x97,0x70,0x4a,0x3e,0x34,0x26,0x1b,0x18, + 0x15,0x15,0x17,0x18,0x18,0x16,0x13,0x0f,0x0f,0x0f,0x0f,0x10,0x10,0x11,0x11,0x15, + 0x18,0x19,0x1a,0x18,0x19,0x18,0x18,0x1c,0x22,0x26,0x29,0x2c,0x2f,0x32,0x35,0x39, + 0x3c,0x3f,0x40,0x3f,0x3e,0x3d,0x42,0x43,0x46,0x4a,0x4e,0x4e,0x4b,0x47,0x45,0x40, + 0xe4,0xe6,0xeb,0xea,0xde,0xdd,0xe0,0xee,0xeb,0xed,0xe9,0xe0,0xe8,0xf0,0xee,0xf0, + 0xe5,0xe0,0xcc,0xc9,0xc8,0xc8,0xc8,0xc9,0xc8,0xc9,0xc6,0xc5,0xb9,0xaf,0xa2,0x95, + 0x86,0x78,0x72,0x76,0x87,0x94,0x9a,0x98,0x96,0x95,0x93,0x95,0x96,0x95,0x95,0x90, + 0x8d,0x8c,0x93,0x9f,0xae,0xb3,0xb2,0xab,0xa3,0xa2,0xa4,0xaa,0xb5,0xc1,0xc8,0xcc, + 0xce,0xd2,0xd8,0xdd,0xe0,0xe1,0xd6,0xc1,0xa5,0x79,0x48,0x39,0x2f,0x22,0x1a,0x18, + 0x15,0x14,0x15,0x15,0x14,0x12,0x10,0x0e,0x0f,0x0f,0x0f,0x10,0x10,0x10,0x11,0x14, + 0x17,0x1a,0x19,0x18,0x17,0x18,0x1a,0x1f,0x24,0x28,0x2b,0x2f,0x32,0x34,0x37,0x3b, + 0x3f,0x40,0x3e,0x3d,0x3c,0x3e,0x42,0x46,0x4b,0x4c,0x4e,0x4f,0x4f,0x4c,0x46,0x40, + 0xe6,0xe9,0xe9,0xed,0xea,0xe1,0xe1,0xe8,0xed,0xf0,0xe3,0xd8,0xf0,0xf1,0xef,0xe0, + 0xe5,0xe2,0xd3,0xc9,0xca,0xc7,0xc8,0xc6,0xc5,0xc3,0xbb,0xb5,0xa3,0x9a,0x94,0x8d, + 0x86,0x7d,0x7f,0x89,0x97,0x9c,0x9e,0x98,0x95,0x93,0x92,0x94,0x95,0x94,0x95,0x86, + 0x7d,0x7f,0x8b,0x9a,0xaa,0xb1,0xb3,0xb4,0xb7,0xb9,0xbe,0xc6,0xce,0xd7,0xdb,0xdf, + 0xe2,0xe4,0xe6,0xe9,0xec,0xea,0xe0,0xca,0xa9,0x7a,0x48,0x36,0x2b,0x1f,0x18,0x16, + 0x14,0x12,0x14,0x14,0x13,0x11,0x10,0x0f,0x0f,0x0f,0x0f,0x10,0x10,0x10,0x10,0x12, + 0x14,0x16,0x15,0x16,0x18,0x18,0x1b,0x1f,0x25,0x2a,0x2f,0x35,0x37,0x37,0x37,0x3a, + 0x3e,0x43,0x40,0x3a,0x39,0x3d,0x41,0x45,0x49,0x4f,0x4d,0x4d,0x4f,0x4d,0x48,0x43, + 0xdc,0xe1,0xe4,0xe9,0xeb,0xe7,0xe9,0xed,0xee,0xea,0xe3,0xdf,0xee,0xef,0xe2,0xea, + 0xe8,0xcf,0xe6,0xd3,0xcb,0xc8,0xc2,0xbc,0xb3,0xae,0xab,0xaa,0xa4,0x9b,0x94,0x8d, + 0x8c,0x8d,0x91,0x97,0x9d,0xa1,0x9f,0x97,0x92,0x90,0x93,0x92,0x90,0x92,0x8d,0x7d, + 0x73,0x78,0x84,0x99,0xa9,0xb0,0xb6,0xc1,0xc6,0xce,0xd8,0xde,0xe2,0xe4,0xe7,0xe7, + 0xe9,0xe8,0xe8,0xe9,0xe8,0xe6,0xdd,0xc6,0xaa,0x79,0x44,0x31,0x27,0x1b,0x16,0x13, + 0x12,0x11,0x13,0x14,0x12,0x11,0x10,0x0f,0x0f,0x0f,0x0f,0x10,0x10,0x10,0x10,0x12, + 0x13,0x13,0x13,0x15,0x16,0x18,0x1c,0x1f,0x25,0x2b,0x34,0x3b,0x3c,0x3c,0x3a,0x3a, + 0x3c,0x3e,0x3d,0x37,0x38,0x3d,0x42,0x45,0x49,0x4e,0x50,0x4f,0x4c,0x4c,0x4d,0x47, + 0xe1,0xdf,0xe5,0xe1,0xe5,0xe1,0xe8,0xeb,0xea,0xe9,0xe9,0xd7,0xe7,0xe5,0xeb,0xe3, + 0xb5,0xa4,0xf0,0xe5,0xc4,0xb6,0xb1,0xad,0xac,0xae,0xae,0xac,0xa4,0x9a,0x97,0x94, + 0x95,0x97,0x97,0x98,0xa0,0xa0,0x9e,0x95,0x8e,0x91,0x91,0x91,0x92,0x92,0x87,0x76, + 0x73,0x79,0x86,0x9b,0xa8,0xad,0xb7,0xc7,0xd3,0xda,0xe2,0xe5,0xe9,0xe7,0xe8,0xe8, + 0xe4,0xe4,0xe3,0xe0,0xe0,0xdd,0xd2,0xbc,0x9c,0x70,0x3f,0x2c,0x23,0x1a,0x16,0x14, + 0x13,0x11,0x11,0x12,0x12,0x0f,0x0f,0x0f,0x0f,0x10,0x11,0x11,0x12,0x10,0x10,0x10, + 0x10,0x13,0x13,0x13,0x15,0x17,0x1b,0x1f,0x21,0x2b,0x36,0x3d,0x3f,0x40,0x3e,0x3b, + 0x3a,0x39,0x38,0x37,0x36,0x3c,0x44,0x48,0x4c,0x50,0x53,0x51,0x4d,0x4c,0x4d,0x4c, + 0xdf,0xdf,0xe6,0xe1,0xe3,0xe8,0xec,0xea,0xed,0xe9,0xe4,0xda,0xe3,0xe8,0xde,0xd9, + 0x94,0xb0,0xe6,0xdd,0xcb,0xb3,0xb2,0xb3,0xaf,0xad,0xad,0xab,0xa8,0xa7,0xa0,0x97, + 0x94,0x93,0x95,0x98,0xa0,0x9f,0x9b,0x93,0x8d,0x8d,0x8e,0x8e,0x90,0x8f,0x83,0x77, + 0x78,0x80,0x8b,0x9a,0xa6,0xab,0xb8,0xcd,0xda,0xe3,0xe9,0xe6,0xe8,0xe6,0xe3,0xdd, + 0xd9,0xd6,0xd2,0xcd,0xca,0xc9,0xbb,0xa4,0x85,0x5e,0x37,0x28,0x20,0x19,0x17,0x14, + 0x14,0x14,0x14,0x12,0x12,0x11,0x11,0x11,0x11,0x11,0x11,0x10,0x11,0x13,0x14,0x13, + 0x13,0x12,0x12,0x12,0x14,0x15,0x19,0x1d,0x22,0x2e,0x3a,0x43,0x46,0x46,0x43,0x40, + 0x3b,0x38,0x37,0x36,0x35,0x38,0x42,0x49,0x4e,0x51,0x55,0x54,0x52,0x50,0x51,0x50, + 0xe1,0xdb,0xe0,0xe2,0xe2,0xe8,0xe4,0xe6,0xec,0xe2,0xea,0xe1,0xd9,0xde,0xe2,0xb9, + 0xa2,0xbd,0xdd,0xd7,0xd6,0xbf,0xb5,0xb0,0xb2,0xb3,0xb1,0xad,0xa7,0x9a,0x92,0x90, + 0x90,0x91,0x93,0x97,0x9f,0x9f,0x99,0x8f,0x87,0x88,0x87,0x8a,0x8f,0x8c,0x86,0x80, + 0x81,0x8a,0x93,0x9e,0xa3,0xaa,0xbc,0xc9,0xd7,0xde,0xdf,0xdd,0xdd,0xd7,0xd2,0xcb, + 0xc3,0xba,0xb1,0xab,0xa4,0xa1,0x93,0x7c,0x5e,0x43,0x2f,0x25,0x1d,0x17,0x16,0x14, + 0x14,0x13,0x12,0x12,0x12,0x13,0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x13,0x14,0x14, + 0x14,0x12,0x12,0x12,0x12,0x12,0x16,0x19,0x20,0x2b,0x3a,0x47,0x4b,0x4b,0x47,0x40, + 0x3a,0x33,0x33,0x34,0x35,0x36,0x3f,0x47,0x4b,0x50,0x55,0x56,0x54,0x54,0x52,0x4f, + 0xe4,0xde,0xe0,0xe0,0xe9,0xe4,0xe7,0xee,0xef,0xea,0xe9,0xd9,0xd6,0xe1,0xd4,0xae, + 0xb1,0xc0,0xdc,0xd4,0xd5,0xcc,0xbc,0xb7,0xb3,0xaa,0xa5,0xa0,0x9b,0x96,0x93,0x91, + 0x90,0x91,0x93,0x98,0x9f,0x9d,0x98,0x89,0x81,0x81,0x84,0x8b,0x90,0x92,0x8e,0x8d, + 0x8f,0x94,0x9c,0xa0,0xa1,0xae,0xbb,0xc7,0xd1,0xd2,0xce,0xc7,0xc2,0xb6,0xab,0xa0, + 0x94,0x86,0x79,0x6f,0x67,0x62,0x5e,0x50,0x3f,0x2b,0x27,0x20,0x1b,0x1a,0x17,0x17, + 0x17,0x16,0x15,0x15,0x14,0x14,0x15,0x15,0x15,0x14,0x14,0x15,0x14,0x14,0x14,0x14, + 0x14,0x14,0x14,0x12,0x10,0x11,0x13,0x16,0x1c,0x25,0x34,0x3f,0x46,0x47,0x43,0x3c, + 0x37,0x31,0x32,0x34,0x36,0x37,0x3e,0x46,0x4a,0x4e,0x56,0x59,0x59,0x5a,0x57,0x53, + 0xd9,0xde,0xdd,0xe2,0xe4,0xe8,0xe5,0xe9,0xee,0xeb,0xe4,0xd1,0xd0,0xe4,0xb3,0xa9, + 0xb2,0xbf,0xc8,0xbd,0xd7,0xd0,0xca,0xa5,0xa0,0x9f,0x9e,0x9d,0x9a,0x96,0x95,0x93, + 0x91,0x91,0x94,0x99,0x9f,0x9b,0x92,0x82,0x7a,0x80,0x87,0x8c,0x92,0x97,0x98,0x98, + 0x99,0x9a,0x9c,0xa0,0xa1,0xad,0xbb,0xc5,0xc9,0xc9,0xbf,0xb2,0xa9,0x99,0x87,0x72, + 0x5f,0x52,0x42,0x39,0x31,0x30,0x2d,0x31,0x2c,0x25,0x23,0x1f,0x1d,0x1b,0x18,0x17, + 0x17,0x15,0x15,0x16,0x15,0x14,0x15,0x15,0x15,0x15,0x14,0x14,0x15,0x15,0x15,0x15, + 0x15,0x15,0x15,0x12,0x11,0x10,0x10,0x14,0x18,0x1f,0x2d,0x38,0x3e,0x3f,0x3a,0x36, + 0x30,0x2c,0x30,0x35,0x38,0x3e,0x3d,0x41,0x47,0x4e,0x55,0x5c,0x5d,0x5b,0x58,0x52, + 0xd2,0xde,0xde,0xe4,0xd8,0xe4,0xf0,0xe6,0xeb,0xef,0xe4,0xcf,0xd0,0xe5,0xae,0xa1, + 0xaf,0x9d,0x90,0xc4,0xdb,0xd0,0xd8,0xb2,0x9f,0xa1,0xa2,0xa0,0x9c,0x99,0x95,0x92, + 0x91,0x92,0x94,0x99,0x9f,0x9a,0x8d,0x7f,0x7a,0x84,0x8a,0x93,0x97,0x9c,0x9d,0x9e, + 0x9f,0x9e,0x9c,0x9e,0xa8,0xb3,0xbe,0xc8,0xca,0xc5,0xb7,0xa7,0x90,0x76,0x5b,0x45, + 0x35,0x2c,0x24,0x26,0x2c,0x2d,0x2c,0x2d,0x2c,0x26,0x25,0x21,0x1f,0x1f,0x1c,0x1b, + 0x1b,0x1b,0x19,0x16,0x16,0x1b,0x1f,0x20,0x20,0x1b,0x18,0x17,0x18,0x18,0x18,0x17, + 0x17,0x15,0x14,0x14,0x14,0x12,0x13,0x13,0x18,0x1e,0x29,0x31,0x36,0x33,0x30,0x2e, + 0x2a,0x28,0x2c,0x30,0x3a,0x3e,0x3e,0x3e,0x42,0x4c,0x52,0x5a,0x5c,0x5c,0x5a,0x54, + 0xd4,0xdf,0xe0,0xd7,0xd6,0xdd,0xed,0xed,0xed,0xea,0xbe,0xc0,0xd0,0xe8,0xb7,0xa2, + 0x9c,0x86,0x9f,0xc2,0xda,0xd3,0xd8,0xce,0xaa,0x9e,0xa1,0x9e,0x9c,0x98,0x95,0x92, + 0x91,0x92,0x95,0x99,0xa0,0x9a,0x8d,0x81,0x7f,0x86,0x8e,0x97,0x9c,0x9f,0xa1,0xa1, + 0xa0,0x9d,0x9c,0xa0,0xaf,0xbb,0xc3,0xc5,0xcc,0xcb,0xc0,0xb2,0x99,0x82,0x6d,0x5a, + 0x46,0x39,0x30,0x2c,0x32,0x31,0x2f,0x2c,0x2c,0x27,0x25,0x23,0x21,0x20,0x1f,0x1f, + 0x1e,0x1e,0x1c,0x1b,0x1f,0x24,0x26,0x2a,0x2a,0x26,0x1d,0x1a,0x18,0x17,0x16,0x17, + 0x18,0x15,0x14,0x13,0x14,0x12,0x13,0x16,0x1c,0x20,0x26,0x2a,0x2e,0x2b,0x2a,0x26, + 0x22,0x26,0x2a,0x30,0x39,0x3d,0x3d,0x3d,0x3f,0x49,0x4c,0x55,0x5b,0x5b,0x5a,0x56, + 0xd5,0xde,0xda,0xd3,0xda,0xdc,0xea,0xed,0xee,0xc2,0xa6,0xa7,0xdc,0xdc,0xbc,0xae, + 0x9a,0xa2,0xaa,0xc5,0xd7,0xd6,0xd0,0xdb,0xc9,0xa7,0xa0,0x9f,0x9b,0x98,0x95,0x93, + 0x92,0x92,0x95,0x98,0x9f,0x9c,0x90,0x89,0x85,0x8e,0x96,0x9c,0x9e,0xa1,0xa0,0x9f, + 0x9e,0x9c,0x9c,0xa6,0xb9,0xc5,0xd0,0xd7,0xe1,0xe1,0xd9,0xcf,0xc1,0xad,0x9d,0x8c, + 0x72,0x63,0x50,0x44,0x3f,0x37,0x3b,0x37,0x31,0x2b,0x2a,0x29,0x26,0x25,0x24,0x25, + 0x22,0x22,0x22,0x20,0x24,0x29,0x2b,0x2e,0x31,0x2c,0x24,0x21,0x1a,0x19,0x17,0x17, + 0x17,0x16,0x15,0x15,0x13,0x14,0x14,0x18,0x20,0x21,0x24,0x29,0x2c,0x29,0x26,0x22, + 0x21,0x23,0x27,0x2d,0x39,0x3f,0x3e,0x3e,0x3e,0x44,0x48,0x4d,0x52,0x57,0x58,0x56, + 0xd6,0xd8,0xce,0xd0,0xd1,0xe0,0xeb,0xeb,0xe8,0xc9,0xb1,0xba,0xe0,0xd0,0xc4,0xc2, + 0x9b,0x9c,0xa6,0xaa,0xd1,0xd9,0xd0,0xdc,0xd5,0xb6,0xa0,0x9f,0x9c,0x98,0x95,0x94, + 0x94,0x93,0x95,0x99,0xa0,0x9e,0x96,0x8e,0x8e,0x94,0x9a,0x9d,0x9d,0x9e,0x9d,0x9b, + 0x9a,0x9b,0x9f,0xb3,0xc6,0xd5,0xde,0xe7,0xec,0xef,0xec,0xe3,0xdb,0xc5,0xbc,0xaf, + 0x9a,0x81,0x6d,0x5b,0x4c,0x43,0x45,0x43,0x39,0x31,0x2c,0x2d,0x2a,0x29,0x29,0x29, + 0x27,0x24,0x22,0x25,0x29,0x2c,0x2f,0x36,0x38,0x35,0x2c,0x25,0x1f,0x1a,0x18,0x18, + 0x17,0x16,0x16,0x15,0x14,0x14,0x14,0x1b,0x1d,0x20,0x27,0x29,0x29,0x27,0x28,0x26, + 0x21,0x1f,0x23,0x2b,0x34,0x3b,0x3d,0x3d,0x3d,0x3d,0x40,0x44,0x4d,0x52,0x55,0x54, + 0xd4,0xcc,0xce,0xd4,0xd6,0xdc,0xee,0xea,0xe1,0xcd,0xba,0xbf,0xc2,0xcd,0xca,0xc5, + 0xa1,0x88,0x9a,0xa4,0xbe,0xdb,0xd1,0xd9,0xda,0xcf,0xaa,0xa0,0x9c,0x98,0x95,0x95, + 0x94,0x93,0x95,0x99,0xa0,0xa0,0x98,0x91,0x93,0x98,0x9b,0x9c,0x9c,0x9c,0x9a,0x98, + 0x98,0x9c,0xb5,0xd8,0xe7,0xec,0xef,0xf1,0xf5,0xf4,0xf2,0xea,0xe5,0xda,0xc8,0xc1, + 0xb3,0x97,0x7f,0x6e,0x61,0x5e,0x5a,0x53,0x48,0x3c,0x31,0x32,0x31,0x32,0x30,0x2d, + 0x2c,0x29,0x27,0x27,0x29,0x2d,0x37,0x3d,0x3f,0x3e,0x36,0x2e,0x27,0x1f,0x19,0x18, + 0x17,0x16,0x16,0x15,0x15,0x14,0x15,0x1b,0x20,0x23,0x28,0x2b,0x2a,0x2b,0x30,0x2e, + 0x2b,0x25,0x22,0x27,0x2d,0x35,0x39,0x39,0x38,0x39,0x3d,0x3f,0x44,0x4a,0x50,0x51, + 0xda,0xda,0xd5,0xd5,0xd6,0xd8,0xe7,0xe8,0xdb,0xba,0xc6,0xa7,0xad,0xc2,0xd3,0xcf, + 0xa9,0x8b,0x8e,0x9c,0xa9,0xd6,0xd2,0xd7,0xdd,0xda,0xc0,0xa0,0x9c,0x98,0x96,0x95, + 0x94,0x94,0x93,0x98,0x9d,0x9e,0x9e,0x9c,0x99,0x9a,0x9b,0x9b,0x9b,0x9a,0x98,0x97, + 0x9e,0xb3,0xd2,0xed,0xf4,0xf3,0xf5,0xf4,0xf4,0xf3,0xf1,0xec,0xe2,0xd8,0xcd,0xc5, + 0xbd,0xaa,0x9f,0x91,0x88,0x7f,0x76,0x67,0x57,0x47,0x37,0x38,0x37,0x36,0x36,0x34, + 0x30,0x2e,0x2c,0x28,0x2a,0x2f,0x39,0x41,0x40,0x40,0x3a,0x34,0x29,0x22,0x1d,0x1a, + 0x18,0x17,0x17,0x15,0x14,0x15,0x17,0x1d,0x22,0x26,0x29,0x2c,0x2c,0x2e,0x35,0x36, + 0x35,0x2f,0x27,0x24,0x28,0x30,0x33,0x34,0x35,0x34,0x34,0x38,0x3c,0x42,0x49,0x4c, + 0xcd,0xcf,0xd1,0xd6,0xd4,0xd9,0xe7,0xe1,0xdb,0xae,0xb0,0xb1,0xbf,0xc8,0xe1,0xd1, + 0xa0,0x8b,0x87,0x90,0xa0,0xcc,0xd6,0xd1,0xe0,0xd7,0xd5,0xa7,0x9b,0x97,0x96,0x95, + 0x94,0x95,0x92,0x98,0x9e,0xa3,0xa7,0xa5,0xa0,0x9c,0x9a,0x9b,0x9c,0x9a,0x9a,0x9b, + 0xa6,0xc3,0xe4,0xf1,0xf6,0xf8,0xf5,0xf2,0xf1,0xef,0xe8,0xe1,0xdb,0xd2,0xd0,0xcc, + 0xcc,0xc8,0xbc,0xaf,0xa8,0xa0,0x8d,0x7d,0x6a,0x50,0x39,0x3a,0x3a,0x38,0x3a,0x3c, + 0x38,0x34,0x32,0x2d,0x2e,0x31,0x37,0x3b,0x3f,0x3f,0x3a,0x33,0x2c,0x26,0x20,0x1c, + 0x1c,0x1a,0x19,0x16,0x17,0x19,0x1a,0x1e,0x23,0x25,0x27,0x2b,0x2f,0x3a,0x39,0x3c, + 0x3c,0x35,0x2b,0x23,0x22,0x27,0x2d,0x30,0x31,0x33,0x32,0x34,0x37,0x3b,0x3e,0x43, + 0xc7,0xca,0xd0,0xd8,0xd6,0xda,0xdf,0xd9,0xdd,0xad,0xaf,0xd5,0xcf,0xd7,0xe9,0xd5, + 0x8f,0x8e,0x8a,0x7d,0x91,0xcd,0xd9,0xcd,0xe1,0xd5,0xdb,0xbb,0x9c,0x99,0x96,0x96, + 0x95,0x94,0x92,0x98,0xa0,0xa8,0xa9,0xa9,0xa7,0x9f,0x9d,0x9c,0x9d,0x9d,0x9d,0xa1, + 0xad,0xc4,0xe5,0xed,0xf4,0xf5,0xef,0xe9,0xe3,0xe1,0xde,0xdb,0xd3,0xd2,0xd3,0xd4, + 0xd1,0xcf,0xc9,0xc0,0xb9,0xb3,0xa2,0x8e,0x77,0x52,0x39,0x3c,0x37,0x37,0x3d,0x40, + 0x40,0x3d,0x37,0x31,0x30,0x33,0x36,0x3a,0x40,0x40,0x3b,0x34,0x2f,0x2b,0x25,0x20, + 0x1d,0x1d,0x1b,0x18,0x18,0x1c,0x1f,0x20,0x22,0x23,0x2a,0x2d,0x32,0x3a,0x40,0x40, + 0x3f,0x38,0x2b,0x20,0x1e,0x21,0x24,0x2a,0x2e,0x30,0x2f,0x30,0x32,0x35,0x37,0x3c, + 0xc6,0xc8,0xcc,0xd1,0xd8,0xdb,0xdd,0xd9,0xdc,0xab,0xbc,0xca,0xc8,0xe1,0xe1,0xc8, + 0x80,0x8f,0x8d,0x71,0x98,0xc4,0xdc,0xcf,0xdb,0xd6,0xde,0xcf,0xaa,0x99,0x98,0x98, + 0x96,0x96,0x94,0x98,0xa0,0xab,0xad,0xac,0xa8,0xa0,0x9d,0x9f,0xa1,0xa1,0xa1,0xa2, + 0xaf,0xbd,0xd8,0xe9,0xec,0xea,0xe5,0xd7,0xd7,0xd1,0xd3,0xd2,0xce,0xd1,0xd4,0xd8, + 0xd7,0xd7,0xd5,0xce,0xc9,0xc1,0xb0,0x9d,0x7e,0x57,0x3d,0x3a,0x38,0x39,0x40,0x45, + 0x45,0x3f,0x3b,0x37,0x32,0x34,0x36,0x3c,0x41,0x44,0x3e,0x37,0x30,0x2e,0x28,0x25, + 0x22,0x21,0x1e,0x1b,0x1b,0x1e,0x22,0x27,0x2b,0x30,0x35,0x34,0x37,0x3f,0x46,0x49, + 0x43,0x3a,0x2f,0x22,0x1c,0x1f,0x25,0x29,0x2e,0x31,0x31,0x33,0x34,0x33,0x33,0x37, + 0xcb,0xcd,0xd2,0xce,0xe3,0xde,0xcb,0xb7,0xd8,0xb1,0xd3,0xd0,0xdc,0xdb,0xcb,0xad, + 0x87,0x8c,0xa3,0x6f,0x9e,0xb6,0xdf,0xd3,0xd8,0xdb,0xda,0xd4,0xbd,0x9b,0x99,0x98, + 0x96,0x95,0x93,0x97,0x9c,0xa5,0xa9,0xa9,0xa5,0x9f,0xa2,0xa3,0xa5,0xa4,0xa0,0x9f, + 0xa3,0xb2,0xbf,0xcd,0xd3,0xcf,0xc6,0xbe,0xc4,0xc9,0xcd,0xce,0xd1,0xd4,0xd7,0xdb, + 0xdd,0xde,0xdb,0xd8,0xd3,0xc7,0xb6,0xa2,0x83,0x5b,0x3c,0x36,0x37,0x3d,0x43,0x45, + 0x44,0x41,0x3f,0x3a,0x36,0x36,0x3b,0x41,0x43,0x42,0x3e,0x39,0x32,0x2d,0x28,0x28, + 0x26,0x23,0x20,0x1f,0x1e,0x22,0x2a,0x33,0x3b,0x3f,0x44,0x42,0x41,0x45,0x4d,0x49, + 0x46,0x3d,0x32,0x25,0x21,0x24,0x29,0x2d,0x33,0x36,0x36,0x35,0x34,0x32,0x34,0x38, + 0xc7,0xc6,0xc9,0xcb,0xd9,0xe1,0xbe,0xac,0xcf,0xbf,0xcd,0xc9,0xc9,0xc9,0xbe,0xad, + 0x93,0x78,0xb6,0x6e,0x8a,0xb5,0xd8,0xd3,0xd4,0xdb,0xda,0xd5,0xcf,0xa6,0x98,0x98, + 0x97,0x95,0x93,0x97,0x9b,0xa1,0xa3,0xa2,0x9f,0xa2,0xa7,0xaa,0xa9,0xa6,0xa6,0xa7, + 0xa9,0xb0,0xb2,0xb5,0xb8,0xba,0xb7,0xbb,0xc2,0xc9,0xcf,0xd2,0xd6,0xda,0xdc,0xdf, + 0xdf,0xde,0xdb,0xd6,0xd0,0xc9,0xbb,0xa4,0x87,0x64,0x40,0x38,0x3b,0x3f,0x43,0x45, + 0x45,0x47,0x42,0x3d,0x3a,0x3b,0x45,0x48,0x48,0x46,0x40,0x3b,0x33,0x30,0x2d,0x2d, + 0x2a,0x29,0x26,0x27,0x26,0x2d,0x37,0x40,0x49,0x4c,0x4f,0x50,0x4f,0x4e,0x4e,0x47, + 0x45,0x3d,0x35,0x2e,0x2b,0x30,0x32,0x37,0x3c,0x3c,0x3a,0x39,0x36,0x33,0x36,0x39, + 0xc2,0xbc,0xc2,0xc8,0xcd,0xdf,0xd4,0xcb,0xc5,0xc2,0xbe,0xb2,0xc4,0xc9,0xcc,0xb1, + 0x98,0x83,0xab,0x7b,0x80,0xb7,0xc9,0xd9,0xd0,0xde,0xd7,0xd4,0xd5,0xbf,0x98,0x99, + 0x97,0x95,0x94,0x98,0x9c,0x9f,0x9f,0xa0,0xa1,0xa7,0xac,0xb1,0xb1,0xb0,0xaf,0xab, + 0xac,0xad,0xaf,0xb1,0xb4,0xb7,0xbc,0xc3,0xcb,0xd0,0xd4,0xd9,0xdc,0xdc,0xdd,0xdd, + 0xdd,0xdb,0xd8,0xd4,0xcf,0xcb,0xbe,0xab,0x90,0x6f,0x50,0x40,0x43,0x46,0x48,0x4a, + 0x48,0x48,0x44,0x40,0x3c,0x41,0x4e,0x51,0x51,0x4e,0x47,0x3e,0x38,0x37,0x35,0x35, + 0x33,0x31,0x30,0x30,0x31,0x39,0x43,0x4c,0x54,0x54,0x58,0x58,0x58,0x53,0x4b,0x45, + 0x40,0x39,0x36,0x33,0x31,0x35,0x38,0x3d,0x3f,0x40,0x3e,0x3d,0x3a,0x3a,0x3a,0x3d, + 0x67,0x94,0xc4,0xca,0xcf,0xd3,0xde,0xd6,0xcc,0xcc,0xcb,0xc8,0xcd,0xd7,0xd6,0xc4, + 0x8f,0x84,0x98,0x81,0x7d,0xa0,0xc7,0xdc,0xd2,0xdf,0xd3,0xd8,0xd1,0xce,0xa7,0x97, + 0x97,0x95,0x95,0x97,0x9b,0x9e,0x9e,0xa2,0xa6,0xab,0xb2,0xb7,0xb7,0xb7,0xb6,0xb1, + 0xaf,0xb0,0xb3,0xb4,0xb8,0xbc,0xc3,0xcb,0xcf,0xd2,0xd5,0xd6,0xd8,0xd8,0xd9,0xdb, + 0xdb,0xdb,0xd9,0xd4,0xd0,0xca,0xc1,0xae,0x97,0x79,0x5d,0x4e,0x50,0x4f,0x4e,0x4f, + 0x4e,0x4a,0x45,0x44,0x47,0x4d,0x58,0x59,0x58,0x55,0x4d,0x44,0x3c,0x3d,0x3d,0x3b, + 0x3d,0x40,0x3e,0x3d,0x3d,0x43,0x4a,0x4f,0x56,0x59,0x5c,0x59,0x55,0x4d,0x47,0x44, + 0x41,0x3c,0x38,0x35,0x32,0x36,0x3a,0x40,0x42,0x45,0x45,0x44,0x43,0x40,0x40,0x40, + 0x54,0x3d,0x57,0x7f,0xb6,0xc3,0xd2,0xdc,0xd3,0xca,0xbf,0xcf,0xb4,0xbc,0xda,0xd3, + 0x91,0x78,0x85,0x76,0x7a,0xa7,0xc6,0xd9,0xd4,0xdf,0xda,0xdc,0xd3,0xd7,0xbd,0x97, + 0x96,0x95,0x93,0x96,0x9a,0xa0,0xa2,0xa6,0xa7,0xad,0xb4,0xb9,0xb9,0xba,0xbc,0xb7, + 0xb6,0xb8,0xb7,0xb9,0xbb,0xc2,0xc4,0xc9,0xce,0xd0,0xd2,0xd4,0xd7,0xda,0xdb,0xdd, + 0xdb,0xdb,0xd7,0xd3,0xcf,0xcc,0xc4,0xb3,0xa0,0x86,0x6e,0x5e,0x5c,0x59,0x57,0x57, + 0x53,0x4f,0x4a,0x4d,0x54,0x5d,0x61,0x63,0x63,0x5d,0x54,0x4c,0x43,0x46,0x47,0x47, + 0x4a,0x4f,0x4e,0x4a,0x47,0x48,0x4e,0x51,0x53,0x59,0x59,0x54,0x4f,0x49,0x44,0x44, + 0x3e,0x3c,0x3b,0x38,0x34,0x33,0x38,0x3c,0x41,0x46,0x48,0x4a,0x4b,0x47,0x45,0x46, + 0x9b,0x40,0x3c,0x40,0x4a,0x68,0x98,0xc3,0xcd,0xb2,0x8a,0x7c,0x76,0xa1,0xe7,0xdd, + 0x9c,0x71,0x77,0x78,0xaa,0xa4,0xba,0xd3,0xd6,0xdc,0xdc,0xdc,0xd7,0xd6,0xcb,0xa2, + 0x97,0x95,0x93,0x97,0x9c,0xa0,0xa2,0xa3,0xa5,0xaa,0xb2,0xb6,0xb9,0xbb,0xbe,0xbd, + 0xbc,0xb9,0xb9,0xbb,0xbc,0xc1,0xc7,0xcd,0xd1,0xd3,0xd5,0xd5,0xd8,0xda,0xdb,0xdd, + 0xdc,0xdb,0xd9,0xd5,0xd1,0xcd,0xc6,0xb9,0xa7,0x92,0x7b,0x67,0x63,0x64,0x60,0x5f, + 0x5c,0x55,0x53,0x57,0x5d,0x65,0x6c,0x6b,0x69,0x63,0x58,0x50,0x50,0x52,0x55,0x58, + 0x5b,0x5d,0x5c,0x56,0x51,0x4f,0x50,0x51,0x4f,0x4d,0x4b,0x45,0x41,0x41,0x3f,0x3f, + 0x3b,0x3c,0x3f,0x3c,0x37,0x33,0x35,0x37,0x3d,0x44,0x49,0x4f,0x51,0x4b,0x49,0x49, + 0xb7,0x85,0x43,0x36,0x3b,0x3b,0x41,0x54,0xa1,0x7a,0x5d,0x62,0x93,0xca,0xe6,0xd2, + 0xb1,0x8d,0x7d,0x84,0xae,0xa6,0xb1,0xca,0xe2,0xdb,0xde,0xdc,0xd5,0xdc,0xcf,0xb9, + 0x98,0x97,0x94,0x98,0x9e,0xa1,0xa0,0x9e,0xa0,0xa4,0xaa,0xb0,0xb5,0xb9,0xb8,0xb4, + 0xb3,0xb3,0xb8,0xbf,0xc7,0xcd,0xce,0xd3,0xd4,0xd5,0xd7,0xd6,0xd8,0xda,0xdc,0xdd, + 0xdb,0xdb,0xd9,0xd7,0xd3,0xcd,0xc8,0xbd,0xaf,0x9b,0x86,0x70,0x6a,0x68,0x65,0x65, + 0x63,0x5f,0x5b,0x61,0x66,0x6b,0x71,0x71,0x6d,0x67,0x5e,0x5a,0x57,0x5b,0x62,0x66, + 0x68,0x6a,0x68,0x62,0x59,0x53,0x52,0x4f,0x49,0x45,0x40,0x39,0x34,0x35,0x37,0x3a, + 0x3a,0x3e,0x40,0x40,0x39,0x35,0x31,0x35,0x3c,0x41,0x49,0x52,0x55,0x51,0x4f,0x4a, + 0xb4,0xb3,0x9d,0x8a,0x71,0x49,0x36,0x44,0x86,0x95,0x63,0x5d,0xb0,0xc5,0xda,0xcc, + 0xaf,0xbb,0x7c,0x7a,0xb1,0xaa,0xad,0xcc,0xde,0xd4,0xe1,0xdc,0xd3,0xdc,0xce,0xc6, + 0xa0,0x98,0x96,0x98,0x9f,0x9e,0x9c,0x98,0x9a,0x9c,0xa0,0xa6,0xac,0xb1,0xb0,0xaf, + 0xb1,0xb4,0xb9,0xc1,0xcb,0xd3,0xd4,0xd7,0xd8,0xd5,0xd5,0xd8,0xd7,0xdb,0xdd,0xdc, + 0xdd,0xdb,0xda,0xd7,0xd5,0xcf,0xca,0xc0,0xb3,0xa3,0x8d,0x77,0x6e,0x6c,0x68,0x65, + 0x65,0x63,0x65,0x67,0x6c,0x72,0x74,0x73,0x6a,0x67,0x60,0x5c,0x58,0x5e,0x67,0x6c, + 0x6d,0x6e,0x6b,0x65,0x5b,0x55,0x4f,0x4b,0x46,0x3e,0x38,0x32,0x2d,0x2f,0x33,0x3a, + 0x3c,0x40,0x42,0x3f,0x3c,0x37,0x33,0x35,0x39,0x3e,0x44,0x50,0x51,0x4f,0x4e,0x49, + 0xc4,0xb1,0xb3,0xba,0xcb,0xbf,0x98,0x65,0x8a,0xb4,0x73,0x61,0x71,0xb4,0xb2,0xad, + 0xac,0xdb,0x84,0x7e,0xa1,0xb9,0xb8,0xb9,0xe0,0xd7,0xdc,0xdc,0xd4,0xdb,0xd1,0xcd, + 0xb0,0x98,0x98,0x9a,0x9f,0x9b,0x96,0x91,0x93,0x97,0x9b,0xa1,0xa8,0xa9,0xa6,0xa7, + 0xad,0xb2,0xb8,0xc2,0xcc,0xd4,0xd5,0xd8,0xd8,0xd5,0xd4,0xd6,0xd8,0xdb,0xdd,0xdc, + 0xdd,0xdc,0xdc,0xd9,0xd6,0xd2,0xcd,0xc4,0xb8,0xaa,0x98,0x7f,0x75,0x6e,0x6a,0x68, + 0x67,0x68,0x69,0x6c,0x70,0x75,0x77,0x71,0x68,0x62,0x5c,0x58,0x57,0x5f,0x68,0x6c, + 0x6e,0x6b,0x67,0x63,0x59,0x54,0x50,0x4e,0x46,0x3c,0x32,0x2b,0x28,0x2c,0x30,0x3e, + 0x45,0x47,0x48,0x47,0x45,0x40,0x3b,0x38,0x3b,0x3c,0x42,0x4b,0x4e,0x4d,0x4b,0x4a, + 0xc8,0xc0,0xac,0xa5,0xa3,0xc6,0xd5,0xba,0x92,0xae,0xa0,0x62,0x5f,0x93,0xaf,0xaf, + 0xb8,0xe1,0x9d,0x72,0x9b,0xb7,0xaa,0x99,0xe6,0xe1,0xd5,0xdb,0xd6,0xd7,0xd3,0xce, + 0xc1,0x9e,0x99,0x9b,0xa0,0x99,0x94,0x8e,0x8f,0x99,0xa0,0xa1,0xa1,0x9f,0x9c,0x97, + 0x9d,0xab,0xb5,0xc0,0xc8,0xce,0xd0,0xd1,0xce,0xcf,0xd0,0xd2,0xd7,0xd9,0xdc,0xdd, + 0xdd,0xdc,0xdd,0xdc,0xd8,0xd5,0xcf,0xc8,0xbc,0xaf,0x9e,0x8e,0x79,0x6e,0x69,0x6d, + 0x6a,0x6b,0x6a,0x69,0x6b,0x6f,0x6e,0x67,0x5f,0x58,0x52,0x4c,0x50,0x56,0x5f,0x66, + 0x6a,0x68,0x60,0x58,0x54,0x56,0x55,0x53,0x4a,0x40,0x35,0x2c,0x28,0x2c,0x31,0x3b, + 0x47,0x4b,0x4d,0x51,0x50,0x4b,0x46,0x3f,0x3d,0x3b,0x40,0x47,0x49,0x4d,0x4c,0x4e, + 0xb7,0xca,0xb5,0xb2,0xa1,0xa1,0xbc,0xcf,0x9b,0x99,0xca,0x62,0x5f,0x72,0xaa,0xa8, + 0x8e,0xc5,0xe7,0x83,0x93,0xc0,0x9a,0x93,0xe7,0xe7,0xd5,0xd8,0xd6,0xd4,0xd3,0xcf, + 0xcc,0xac,0x9a,0x9b,0x9f,0x99,0x93,0x8c,0x8e,0x9d,0xa2,0x9d,0x9a,0x97,0x94,0x90, + 0x97,0x9b,0xa9,0xb4,0xbd,0xc4,0xc7,0xc7,0xc8,0xc8,0xca,0xcd,0xd1,0xd7,0xdb,0xdd, + 0xdf,0xdd,0xde,0xdc,0xda,0xd7,0xd3,0xcc,0xc3,0xb6,0xa6,0x96,0x80,0x70,0x6b,0x69, + 0x6b,0x68,0x68,0x68,0x65,0x67,0x62,0x5a,0x51,0x4c,0x43,0x3f,0x49,0x4e,0x54,0x59, + 0x60,0x60,0x58,0x52,0x50,0x54,0x57,0x55,0x4f,0x46,0x3a,0x2e,0x28,0x2b,0x33,0x39, + 0x42,0x49,0x4d,0x56,0x56,0x53,0x4e,0x4b,0x43,0x3c,0x41,0x42,0x44,0x4a,0x4d,0x50, + 0xbb,0xbb,0xcc,0xb7,0xad,0xa0,0x93,0x8d,0xa7,0xcd,0xd3,0x63,0x5e,0x5e,0x82,0x94, + 0x81,0xac,0xfd,0xae,0x8a,0xc7,0x7f,0x9c,0xe0,0xe7,0xd6,0xd6,0xd5,0xd1,0xd4,0xd0, + 0xce,0xbe,0x9c,0x9b,0xa1,0x9b,0x93,0x8b,0x8e,0x9b,0x9f,0x9a,0x97,0x96,0x95,0x95, + 0x9c,0xa7,0xb0,0xb2,0xb8,0xba,0xc0,0xbf,0xbb,0xbb,0xc1,0xc5,0xca,0xd1,0xd7,0xd9, + 0xdc,0xde,0xde,0xde,0xdc,0xd8,0xd5,0xce,0xc8,0xbd,0xb0,0x9f,0x8a,0x74,0x6b,0x64, + 0x65,0x64,0x64,0x62,0x60,0x5e,0x57,0x4b,0x45,0x41,0x37,0x35,0x3d,0x45,0x4a,0x4d, + 0x54,0x58,0x51,0x4f,0x52,0x54,0x59,0x5b,0x56,0x4c,0x3f,0x32,0x2c,0x2d,0x34,0x38, + 0x40,0x44,0x4c,0x53,0x56,0x54,0x55,0x52,0x46,0x40,0x3e,0x3e,0x41,0x49,0x4f,0x54, + 0xc6,0xb9,0xc6,0xc4,0xaf,0xa8,0x97,0x9b,0xab,0xcb,0xd5,0x62,0x5e,0x5e,0x67,0x7e, + 0x7d,0x9e,0xf8,0xd1,0x9d,0xc2,0x85,0x9b,0xde,0xe4,0xdf,0xd2,0xd3,0xcf,0xd4,0xcd, + 0xcd,0xca,0xa5,0x9f,0xa1,0x9b,0x93,0x8b,0x8c,0x98,0x9b,0x98,0x94,0x94,0x93,0x9c, + 0xb0,0xbf,0xc7,0xc9,0xc8,0xce,0xcd,0xcb,0xc6,0xb7,0xb6,0xba,0xc2,0xca,0xce,0xd4, + 0xd6,0xd9,0xda,0xdd,0xde,0xdb,0xd9,0xd1,0xcb,0xc2,0xb5,0xa8,0x95,0x7c,0x6b,0x5e, + 0x5e,0x5c,0x59,0x56,0x54,0x51,0x49,0x3f,0x3c,0x39,0x2f,0x2b,0x33,0x3e,0x47,0x49, + 0x50,0x54,0x53,0x52,0x58,0x5e,0x63,0x63,0x5e,0x52,0x44,0x38,0x32,0x34,0x39,0x3d, + 0x41,0x41,0x48,0x4e,0x51,0x54,0x57,0x52,0x48,0x43,0x3d,0x3f,0x44,0x4d,0x59,0x61, + 0xc6,0xc4,0xbc,0xcc,0xba,0xb0,0xa3,0x9c,0x90,0xa3,0xc3,0x9e,0x5d,0x60,0x61,0x67, + 0x73,0x8b,0xea,0xee,0xca,0xbb,0x84,0x8f,0xe3,0xde,0xe2,0xd0,0xd1,0xcd,0xd2,0xcd, + 0xcd,0xcc,0xb3,0x9e,0xa3,0x9c,0x94,0x8c,0x8c,0x95,0x98,0x95,0x92,0x94,0x94,0xab, + 0xc2,0xd1,0xd8,0xdb,0xdc,0xdf,0xdb,0xd7,0xd4,0xce,0xc2,0xc8,0xca,0xcc,0xc9,0xcc, + 0xd0,0xd3,0xd5,0xd7,0xd8,0xdb,0xd7,0xd3,0xcd,0xc6,0xbc,0xae,0x9b,0x7f,0x6a,0x57, + 0x56,0x53,0x50,0x48,0x43,0x3f,0x39,0x36,0x36,0x37,0x2e,0x2c,0x33,0x3b,0x44,0x4c, + 0x53,0x55,0x56,0x59,0x62,0x69,0x6c,0x6b,0x64,0x5a,0x4f,0x43,0x40,0x3e,0x3f,0x42, + 0x43,0x40,0x43,0x49,0x4c,0x51,0x53,0x4e,0x44,0x41,0x3f,0x3f,0x44,0x4f,0x5a,0x66, + 0xc4,0xc4,0xc6,0xcc,0xca,0xb5,0xae,0xa8,0x9a,0x95,0xb5,0xe0,0x7e,0x5f,0x5e,0x5f, + 0x63,0x78,0xcc,0xfc,0xf3,0xc0,0xa8,0xa3,0xda,0xda,0xde,0xd1,0xd1,0xcc,0xd0,0xcd, + 0xcc,0xcd,0xc0,0xa1,0xa3,0x9c,0x94,0x8b,0x8c,0x96,0x97,0x92,0x90,0x95,0xa2,0xbf, + 0xd3,0xdc,0xe3,0xe6,0xe9,0xea,0xe7,0xe7,0xe7,0xe0,0xdd,0xdb,0xd2,0xcf,0xcf,0xc9, + 0xcf,0xd2,0xd3,0xd3,0xd4,0xd7,0xd5,0xd3,0xd0,0xca,0xc2,0xb5,0xa2,0x8a,0x6a,0x4f, + 0x4a,0x48,0x41,0x3b,0x36,0x31,0x30,0x33,0x35,0x33,0x30,0x30,0x35,0x3b,0x45,0x50, + 0x57,0x59,0x5b,0x60,0x69,0x72,0x75,0x73,0x6b,0x61,0x59,0x51,0x4e,0x4a,0x47,0x45, + 0x44,0x42,0x42,0x45,0x44,0x48,0x4a,0x45,0x40,0x40,0x42,0x43,0x48,0x4f,0x55,0x63, + 0xbd,0xc3,0xc8,0xcc,0xd1,0xc6,0xb2,0xaf,0xa4,0x9d,0x90,0xa2,0x7f,0x65,0x5c,0x5d, + 0x5c,0x67,0x95,0xee,0xf8,0xe3,0xc0,0xb1,0xc6,0xd9,0xe2,0xd4,0xd1,0xce,0xce,0xcd, + 0xcc,0xcc,0xca,0xa9,0xa2,0x9e,0x95,0x8d,0x8f,0x98,0x99,0x94,0x8f,0x95,0xab,0xc7, + 0xdc,0xe5,0xeb,0xee,0xf0,0xf0,0xef,0xef,0xee,0xea,0xec,0xe6,0xdd,0xd6,0xd3,0xcf, + 0xd2,0xd5,0xd4,0xd2,0xd1,0xd0,0xd0,0xcf,0xce,0xc9,0xc7,0xbc,0xa9,0x90,0x6c,0x4e, + 0x40,0x3a,0x34,0x32,0x2e,0x2a,0x2d,0x31,0x32,0x32,0x33,0x37,0x38,0x3a,0x42,0x4d, + 0x54,0x56,0x5b,0x63,0x6e,0x78,0x7b,0x7b,0x74,0x6b,0x64,0x5e,0x5b,0x5a,0x56,0x4f, + 0x4a,0x47,0x44,0x43,0x42,0x41,0x41,0x40,0x3f,0x43,0x44,0x45,0x4a,0x50,0x55,0x61, + 0xb4,0xb8,0xc1,0xcb,0xd0,0xd1,0xc3,0xb1,0xab,0xa2,0x9d,0x93,0x79,0x71,0x78,0x5e, + 0x5d,0x5f,0x83,0xda,0xf6,0xf2,0xd9,0xcc,0xbb,0xd2,0xe5,0xdf,0xd2,0xcd,0xcc,0xce, + 0xcd,0xcc,0xcd,0xb4,0xa0,0x9d,0x95,0x90,0x91,0x99,0x98,0x93,0x8f,0x99,0xb1,0xca, + 0xe0,0xe9,0xf0,0xf0,0xf1,0xf5,0xf6,0xf6,0xf5,0xf3,0xf5,0xef,0xe6,0xdd,0xdb,0xd4, + 0xd9,0xdb,0xd6,0xd1,0xcd,0xca,0xc7,0xc8,0xca,0xc9,0xc6,0xc3,0xb2,0x98,0x72,0x50, + 0x39,0x30,0x2b,0x29,0x26,0x26,0x2c,0x32,0x33,0x35,0x36,0x39,0x36,0x37,0x3d,0x48, + 0x4c,0x50,0x57,0x65,0x6e,0x76,0x7f,0x80,0x78,0x70,0x6b,0x62,0x62,0x63,0x63,0x5d, + 0x59,0x55,0x4d,0x46,0x40,0x3c,0x3b,0x3f,0x43,0x4b,0x49,0x48,0x4e,0x54,0x56,0x5f, + 0xad,0xb1,0xb5,0xc5,0xcc,0xd1,0xd5,0xb9,0xae,0xa8,0xa1,0xa6,0x94,0x95,0xaa,0x71, + 0x63,0x5f,0x6d,0xb9,0xeb,0xf8,0xeb,0xd0,0xcd,0xad,0xe3,0xe3,0xd8,0xcb,0xcb,0xcc, + 0xcd,0xcc,0xce,0xbf,0xa1,0x9b,0x96,0x90,0x91,0x98,0x98,0x92,0x90,0x9b,0xb2,0xcc, + 0xe1,0xeb,0xef,0xf0,0xf1,0xf1,0xf2,0xf6,0xf7,0xf4,0xf6,0xf4,0xea,0xe4,0xde,0xda, + 0xe0,0xe2,0xd9,0xd2,0xca,0xc1,0xbd,0xbe,0xbf,0xc2,0xc2,0xc0,0xb2,0x9b,0x75,0x50, + 0x36,0x2d,0x29,0x29,0x26,0x27,0x2c,0x2f,0x31,0x34,0x37,0x38,0x36,0x36,0x3b,0x41, + 0x42,0x46,0x4e,0x5c,0x64,0x6f,0x76,0x7a,0x74,0x70,0x6a,0x67,0x64,0x68,0x67,0x67, + 0x65,0x5e,0x56,0x4d,0x47,0x43,0x3f,0x43,0x4c,0x51,0x51,0x4f,0x4e,0x51,0x53,0x5d, + 0xaf,0xae,0xb7,0xb9,0xc9,0xd2,0xd5,0xce,0xb6,0xac,0xa8,0xa4,0xa4,0xa3,0x98,0x92, + 0x76,0x64,0x6a,0x7e,0xd1,0xf5,0xef,0xdb,0xe2,0xb8,0xe6,0xea,0xd9,0xcc,0xcb,0xcb, + 0xcc,0xcd,0xcb,0xc8,0xa3,0x9a,0x95,0x93,0x92,0x97,0x96,0x91,0x8f,0x9a,0xb3,0xcc, + 0xe0,0xea,0xef,0xf0,0xf1,0xf1,0xf4,0xf7,0xf6,0xf8,0xf7,0xf2,0xe9,0xe0,0xda,0xdd, + 0xe3,0xe7,0xe0,0xd4,0xc8,0xba,0xb3,0xb3,0xb5,0xb8,0xba,0xba,0xad,0x99,0x79,0x52, + 0x36,0x30,0x2c,0x2d,0x2a,0x2b,0x2e,0x33,0x32,0x32,0x33,0x31,0x33,0x32,0x39,0x3e, + 0x3a,0x39,0x40,0x4f,0x5a,0x63,0x6b,0x6c,0x68,0x67,0x66,0x68,0x66,0x69,0x69,0x6a, + 0x68,0x60,0x5b,0x56,0x4f,0x47,0x46,0x4a,0x50,0x56,0x57,0x59,0x57,0x55,0x56,0x5e, + 0xb1,0xac,0xaf,0xb4,0xb9,0xc3,0xcc,0xd3,0xc8,0xb4,0xab,0xa8,0x9d,0xa1,0x9a,0x99, + 0x94,0x70,0x60,0x64,0x86,0xdc,0xf5,0xed,0xe6,0xce,0xe4,0xec,0xda,0xcc,0xcb,0xcc, + 0xcc,0xcb,0xcc,0xcb,0xaa,0x96,0x94,0x94,0x92,0x94,0x93,0x8d,0x8c,0x99,0xb5,0xcd, + 0xe0,0xe9,0xee,0xf0,0xf0,0xf0,0xf2,0xf4,0xf5,0xf7,0xf7,0xf3,0xe7,0xdf,0xdb,0xdc, + 0xe4,0xe8,0xe0,0xd6,0xc6,0xb6,0xa8,0xa5,0xa5,0xa9,0xad,0xaf,0xa5,0x94,0x77,0x54, + 0x37,0x2e,0x2d,0x2d,0x2c,0x2c,0x2e,0x31,0x30,0x30,0x2d,0x2b,0x2c,0x2b,0x31,0x36, + 0x2f,0x2c,0x30,0x3f,0x45,0x51,0x5a,0x5f,0x5c,0x5b,0x5e,0x64,0x65,0x66,0x67,0x67, + 0x63,0x5d,0x5b,0x57,0x52,0x4a,0x4b,0x4d,0x50,0x58,0x5c,0x5e,0x5c,0x5c,0x5c,0x60, + 0xb1,0xae,0xae,0xae,0xb0,0xb8,0xc5,0xc6,0xd3,0xc4,0xb2,0xab,0xa7,0xa2,0x9f,0x9c, + 0x89,0x85,0x7a,0x63,0x66,0x9d,0xf4,0xf4,0xee,0xe0,0xe2,0xec,0xdd,0xcd,0xcb,0xcb, + 0xcb,0xcb,0xcc,0xcd,0xb7,0x99,0x96,0x91,0x93,0x92,0x90,0x89,0x89,0x99,0xb3,0xcb, + 0xdf,0xe8,0xee,0xef,0xf0,0xef,0xf1,0xf4,0xf5,0xf6,0xf7,0xf2,0xe7,0xde,0xd8,0xd9, + 0xe1,0xe6,0xdf,0xd4,0xc3,0xb0,0xa1,0x9b,0x95,0x96,0x9d,0xa4,0x9c,0x89,0x74,0x57, + 0x3c,0x31,0x31,0x2f,0x2e,0x2d,0x2d,0x2e,0x2e,0x2d,0x2b,0x29,0x29,0x27,0x29,0x2d, + 0x2b,0x28,0x2b,0x32,0x36,0x3f,0x4a,0x50,0x51,0x51,0x53,0x58,0x5d,0x5f,0x5f,0x60, + 0x5b,0x57,0x55,0x56,0x51,0x4d,0x4e,0x50,0x51,0x56,0x5c,0x62,0x64,0x65,0x66,0x6a, + 0xb8,0xab,0xac,0xae,0xac,0xaf,0xb1,0xbc,0xc6,0xcc,0xc1,0xb2,0xa9,0xac,0xa4,0x9f, + 0xa0,0x9b,0x95,0x80,0x6b,0x6d,0xcd,0xf6,0xf2,0xda,0xd0,0xe8,0xe1,0xcd,0xcc,0xcb, + 0xcb,0xcb,0xcc,0xcc,0xc0,0x9c,0x95,0x8f,0x90,0x90,0x8e,0x89,0x88,0x96,0xb2,0xca, + 0xdd,0xe6,0xee,0xee,0xf0,0xf0,0xf1,0xf3,0xf4,0xf5,0xf5,0xf1,0xe7,0xdb,0xd3,0xd6, + 0xdf,0xe3,0xdd,0xd1,0xc0,0xaa,0x9d,0x95,0x8c,0x86,0x89,0x94,0x8f,0x82,0x6e,0x57, + 0x3d,0x34,0x31,0x30,0x2b,0x29,0x28,0x2b,0x2b,0x2b,0x2a,0x29,0x28,0x27,0x28,0x2b, + 0x2b,0x28,0x27,0x29,0x2c,0x2e,0x36,0x3d,0x40,0x43,0x47,0x4d,0x53,0x55,0x56,0x57, + 0x55,0x52,0x54,0x53,0x50,0x4f,0x51,0x4f,0x4e,0x54,0x59,0x60,0x65,0x6b,0x6f,0x72, + 0xe2,0xcf,0xd1,0xc8,0xc4,0xcb,0xc2,0xb9,0xbf,0xc6,0xc7,0xc1,0xb1,0xac,0xa9,0xa3, + 0x9b,0xa2,0xaa,0x88,0x7f,0x74,0x95,0xf6,0xf9,0xdb,0xd5,0xe4,0xe3,0xcc,0xcb,0xcb, + 0xca,0xca,0xcc,0xcc,0xc7,0x9c,0x8d,0x89,0x8b,0x8d,0x8b,0x86,0x86,0x95,0xb1,0xc9, + 0xdc,0xe7,0xec,0xec,0xef,0xef,0xf0,0xf2,0xf3,0xf5,0xf5,0xef,0xe3,0xd7,0xcc,0xd3, + 0xdc,0xe1,0xda,0xce,0xbc,0xa6,0x95,0x8d,0x85,0x7e,0x7e,0x88,0x8b,0x80,0x6f,0x5c, + 0x41,0x37,0x33,0x30,0x2c,0x2b,0x2b,0x2e,0x2d,0x2b,0x29,0x29,0x28,0x28,0x28,0x2b, + 0x29,0x25,0x23,0x23,0x25,0x28,0x2b,0x31,0x38,0x3f,0x44,0x48,0x4d,0x4d,0x4e,0x4e, + 0x4f,0x4e,0x4f,0x50,0x50,0x52,0x4d,0x4c,0x4c,0x4f,0x55,0x5e,0x68,0x70,0x74,0x77, + 0xf0,0xeb,0xef,0xf0,0xe6,0xec,0xef,0xe3,0xe2,0xe2,0xdf,0xdd,0xd5,0xcb,0xc3,0xb7, + 0xa8,0xa1,0x9e,0xa2,0x97,0x8f,0x89,0xe0,0xf5,0xec,0xe8,0xe5,0xde,0xce,0xcc,0xcb, + 0xca,0xca,0xca,0xcb,0xcb,0x9c,0x80,0x80,0x85,0x8a,0x89,0x83,0x83,0x92,0xae,0xc6, + 0xda,0xe5,0xeb,0xec,0xed,0xed,0xee,0xf0,0xf0,0xf3,0xf2,0xec,0xdd,0xcf,0xc8,0xce, + 0xd8,0xdf,0xd8,0xcb,0xb8,0xa0,0x8f,0x87,0x7f,0x7b,0x7c,0x88,0x89,0x80,0x70,0x5c, + 0x43,0x37,0x33,0x2e,0x2b,0x2c,0x2e,0x32,0x31,0x2d,0x2a,0x29,0x28,0x28,0x28,0x28, + 0x25,0x24,0x22,0x22,0x22,0x27,0x2d,0x32,0x36,0x3b,0x3e,0x42,0x48,0x49,0x4a,0x4b, + 0x4b,0x4c,0x4c,0x4e,0x4c,0x4b,0x4b,0x4b,0x4c,0x50,0x56,0x5d,0x68,0x6e,0x72,0x75, + 0xed,0xeb,0xef,0xef,0xe5,0xe6,0xe5,0xdf,0xe0,0xe5,0xe6,0xe4,0xe4,0xe4,0xe4,0xe7, + 0xe6,0xde,0xda,0xd3,0xc7,0xbb,0xd6,0xe9,0xf6,0xf5,0xf2,0xed,0xdd,0xcf,0xcc,0xcb, + 0xca,0xcb,0xca,0xc9,0xca,0xa2,0x6d,0x72,0x7c,0x84,0x83,0x80,0x7f,0x8f,0xa9,0xc2, + 0xd5,0xe0,0xe6,0xe7,0xea,0xea,0xeb,0xeb,0xed,0xee,0xee,0xe5,0xd5,0xc8,0xc4,0xcb, + 0xd5,0xdd,0xd6,0xc8,0xb5,0x9e,0x8c,0x83,0x7d,0x7d,0x83,0x90,0x8f,0x86,0x75,0x5f, + 0x47,0x3a,0x33,0x30,0x2f,0x32,0x34,0x35,0x34,0x2e,0x2b,0x29,0x28,0x28,0x28,0x28, + 0x27,0x27,0x25,0x27,0x27,0x29,0x2f,0x36,0x3a,0x3e,0x41,0x44,0x49,0x4a,0x4c,0x4e, + 0x4f,0x50,0x4d,0x4c,0x4e,0x4f,0x4e,0x4c,0x4b,0x50,0x58,0x5e,0x68,0x6d,0x72,0x76, + 0xf0,0xec,0xe8,0xef,0xe9,0xe4,0xea,0xdf,0xe3,0xe7,0xe4,0xe4,0xe1,0xe0,0xe0,0xe1, + 0xe0,0xde,0xe3,0xe2,0xe8,0xe7,0xe9,0xeb,0xee,0xf4,0xf2,0xf0,0xe2,0xd2,0xcb,0xcd, + 0xcc,0xca,0xc9,0xca,0xca,0xb3,0x68,0x6a,0x71,0x78,0x7a,0x79,0x7a,0x87,0x9f,0xb7, + 0xcb,0xd7,0xdc,0xdc,0xe1,0xe0,0xe0,0xe0,0xe3,0xe6,0xe1,0xd8,0xc7,0xbf,0xbe,0xc6, + 0xd4,0xda,0xd1,0xc0,0xaf,0x9a,0x86,0x7f,0x7e,0x85,0x8e,0x98,0x96,0x8b,0x78,0x5f, + 0x47,0x3b,0x34,0x32,0x35,0x38,0x37,0x36,0x37,0x31,0x2d,0x2a,0x29,0x28,0x27,0x2b, + 0x2c,0x2d,0x2c,0x2c,0x2c,0x2d,0x37,0x3c,0x3e,0x43,0x45,0x47,0x4a,0x4c,0x4f,0x54, + 0x54,0x53,0x50,0x4f,0x4f,0x50,0x4e,0x4b,0x4b,0x4f,0x5c,0x5f,0x66,0x6b,0x71,0x75, + 0xec,0xef,0xe9,0xeb,0xee,0xe5,0xeb,0xe3,0xe7,0xe5,0xe2,0xe3,0xe0,0xe0,0xe0,0xde, + 0xe3,0xe2,0xe6,0xe0,0xe9,0xe0,0xe7,0xe7,0xe9,0xea,0xed,0xef,0xee,0xe1,0xd9,0xd2, + 0xcc,0xca,0xc8,0xc8,0xc9,0xc0,0x70,0x67,0x6f,0x6f,0x6e,0x6f,0x74,0x7f,0x91,0xa8, + 0xb9,0xc3,0xca,0xce,0xce,0xcd,0xce,0xce,0xd0,0xd1,0xcc,0xc3,0xb5,0xb1,0xb2,0xbd, + 0xc7,0xcd,0xc4,0xb6,0xa7,0x93,0x84,0x85,0x8a,0x91,0x9a,0x9e,0x9b,0x90,0x76,0x5b, + 0x45,0x3c,0x35,0x37,0x3d,0x3e,0x3d,0x3b,0x3b,0x36,0x30,0x2c,0x27,0x27,0x2a,0x2f, + 0x2f,0x30,0x34,0x37,0x36,0x3a,0x3e,0x43,0x47,0x4c,0x4b,0x4b,0x4f,0x51,0x52,0x58, + 0x57,0x54,0x4f,0x4f,0x4c,0x48,0x4a,0x49,0x4c,0x55,0x62,0x64,0x67,0x6b,0x6f,0x72, + 0xe7,0xed,0xeb,0xe7,0xec,0xea,0xe7,0xe4,0xe0,0xe6,0xe4,0xe8,0xe1,0xde,0xde,0xde, + 0xe2,0xe5,0xe3,0xeb,0xe2,0xe3,0xe8,0xe9,0xea,0xec,0xf0,0xf0,0xed,0xea,0xea,0xe9, + 0xe4,0xd9,0xcf,0xca,0xc9,0xc3,0x78,0x63,0x65,0x66,0x64,0x65,0x69,0x75,0x86,0x9b, + 0xa4,0xae,0xb2,0xb3,0xb6,0xb5,0xb5,0xb5,0xb3,0xb3,0xb0,0xa7,0x9c,0x9e,0xa4,0xaf, + 0xb9,0xbe,0xb2,0xa7,0x98,0x91,0x8c,0x90,0x95,0x9b,0xa2,0xa5,0x9c,0x8d,0x6f,0x52, + 0x40,0x3b,0x36,0x3b,0x40,0x3f,0x41,0x40,0x3c,0x37,0x2d,0x2a,0x29,0x28,0x2b,0x30, + 0x33,0x38,0x3c,0x3f,0x3f,0x41,0x44,0x4a,0x50,0x51,0x4c,0x49,0x4d,0x50,0x50,0x54, + 0x56,0x54,0x4e,0x4b,0x45,0x42,0x45,0x48,0x52,0x5d,0x64,0x69,0x6d,0x6e,0x6f,0x6e, + 0xeb,0xe9,0xeb,0xed,0xe6,0xea,0xe9,0xe8,0xe4,0xe2,0xe7,0xe5,0xe5,0xe1,0xe0,0xde, + 0xde,0xe3,0xdf,0xe6,0xe2,0xe6,0xe8,0xea,0xec,0xed,0xec,0xec,0xed,0xeb,0xeb,0xec, + 0xec,0xec,0xe9,0xde,0xcf,0xa3,0x64,0x6a,0x68,0x64,0x60,0x61,0x64,0x71,0x82,0x93, + 0x9e,0xa2,0xa1,0xa2,0xa0,0x9d,0x9c,0x9d,0x9a,0x97,0x92,0x8f,0x87,0x8b,0x94,0x9f, + 0xa4,0xa6,0x9f,0x9a,0x97,0x96,0x99,0x9e,0xa3,0xa6,0xa9,0xa8,0x9a,0x85,0x68,0x4a, + 0x3b,0x39,0x3a,0x3d,0x3f,0x42,0x42,0x40,0x3d,0x34,0x2e,0x2c,0x2c,0x2c,0x2f,0x34, + 0x3c,0x3f,0x45,0x49,0x49,0x4a,0x4b,0x4f,0x52,0x50,0x4d,0x4a,0x4d,0x4e,0x4e,0x52, + 0x53,0x53,0x4d,0x4a,0x42,0x3d,0x3f,0x49,0x53,0x5b,0x64,0x69,0x6d,0x6f,0x6e,0x6b, + 0xee,0xef,0xec,0xed,0xe8,0xe9,0xe9,0xe6,0xe5,0xe6,0xe3,0xe8,0xe7,0xe3,0xe1,0xe2, + 0xe0,0xe0,0xe1,0xe0,0xe7,0xe7,0xec,0xec,0xec,0xeb,0xe9,0xea,0xe7,0xe5,0xe7,0xe6, + 0xe4,0xe2,0xdb,0xd1,0xb9,0x72,0x65,0x75,0x6e,0x67,0x65,0x60,0x63,0x71,0x7f,0x8d, + 0x9c,0xa3,0xa2,0x9f,0x9c,0x9b,0x9b,0x9b,0x9c,0x9b,0x96,0x8d,0x87,0x83,0x89,0x8f, + 0x92,0x91,0x96,0x98,0x9d,0xa3,0xa6,0xa8,0xab,0xae,0xaf,0xa6,0x93,0x78,0x5f,0x44, + 0x38,0x39,0x3c,0x40,0x44,0x45,0x44,0x43,0x3c,0x36,0x31,0x31,0x30,0x30,0x33,0x38, + 0x40,0x45,0x4a,0x4b,0x4d,0x4d,0x4c,0x4f,0x4e,0x4d,0x49,0x49,0x4a,0x4b,0x4c,0x4f, + 0x51,0x51,0x4c,0x48,0x43,0x3f,0x3f,0x44,0x4e,0x55,0x5f,0x64,0x68,0x63,0x64,0x65, + 0xb2,0xd4,0xe3,0xe8,0xe5,0xe7,0xe6,0xe6,0xe7,0xe6,0xe6,0xe4,0xe5,0xe7,0xe8,0xe5, + 0xe7,0xe8,0xe7,0xe2,0xe5,0xe5,0xe1,0xdc,0xd3,0xd4,0xc9,0xbc,0xb4,0xb5,0xb1,0xae, + 0xa9,0x9a,0x8d,0x91,0x8e,0x6e,0x73,0x7b,0x73,0x71,0x6d,0x68,0x68,0x72,0x7c,0x8b, + 0x99,0xa5,0xac,0xae,0xab,0xa9,0xa9,0xab,0xaa,0xa9,0xa5,0x9f,0x99,0x93,0x92,0x93, + 0x93,0x96,0x9d,0xa4,0xac,0xaf,0xb2,0xb3,0xb4,0xb1,0xac,0xa1,0x8d,0x72,0x56,0x43, + 0x3a,0x3d,0x42,0x45,0x47,0x49,0x48,0x44,0x41,0x3d,0x3d,0x3a,0x36,0x35,0x38,0x3e, + 0x44,0x46,0x49,0x4e,0x4e,0x4e,0x4c,0x4d,0x4b,0x49,0x46,0x48,0x47,0x47,0x4b,0x50, + 0x4e,0x4d,0x4b,0x49,0x45,0x40,0x3e,0x41,0x49,0x52,0x58,0x5f,0x5e,0x59,0x5b,0x58, + 0x3d,0x48,0x63,0x7f,0x82,0x87,0x8f,0x99,0x9f,0xa3,0xa1,0x9d,0xa2,0x9a,0x96,0x9a, + 0xa2,0x9c,0x87,0x88,0x86,0x85,0x7f,0x7c,0x84,0x8c,0x96,0x99,0x95,0x92,0x94,0x92, + 0x91,0x7f,0x7c,0x80,0x7c,0x60,0x6b,0x75,0x74,0x72,0x72,0x6e,0x6a,0x6f,0x7c,0x89, + 0x9a,0xa8,0xb1,0xb6,0xba,0xb8,0xb6,0xb5,0xb6,0xb5,0xb1,0xac,0xa9,0xa2,0xa1,0xa4, + 0xa2,0xa7,0xab,0xb1,0xb7,0xba,0xbb,0xb8,0xb6,0xb2,0xaa,0x9a,0x85,0x68,0x4f,0x40, + 0x3c,0x43,0x46,0x48,0x48,0x48,0x47,0x45,0x40,0x3f,0x3f,0x3c,0x3a,0x38,0x3c,0x41, + 0x44,0x45,0x4b,0x4e,0x4e,0x4e,0x4b,0x4a,0x46,0x45,0x46,0x49,0x48,0x48,0x4c,0x50, + 0x4e,0x4c,0x4c,0x4a,0x45,0x40,0x41,0x3f,0x46,0x4c,0x4f,0x52,0x51,0x4b,0x4f,0x4b, + 0x4b,0x4b,0x5b,0x58,0x55,0x5a,0x68,0x68,0x6e,0x7a,0x68,0x5b,0x6a,0x70,0x76,0x7b, + 0x7f,0x87,0x82,0x7e,0x79,0x70,0x7a,0x8c,0x84,0x7b,0x81,0x87,0x8a,0x8c,0x94,0x88, + 0x6c,0x5f,0x69,0x6f,0x6e,0x5c,0x6d,0x74,0x73,0x74,0x74,0x74,0x70,0x71,0x76,0x81, + 0x91,0xa0,0xad,0xb3,0xbc,0xc0,0xc0,0xc0,0xc1,0xc1,0xba,0xb6,0xb2,0xb0,0xb0,0xb1, + 0xb2,0xb5,0xba,0xbd,0xbf,0xc2,0xbf,0xba,0xb5,0xae,0xa5,0x94,0x7b,0x63,0x4e,0x42, + 0x43,0x47,0x48,0x4a,0x4a,0x49,0x48,0x48,0x43,0x42,0x43,0x40,0x3c,0x3d,0x44,0x45, + 0x47,0x49,0x4e,0x50,0x4e,0x4d,0x4b,0x4a,0x47,0x44,0x48,0x4a,0x4b,0x4c,0x4f,0x4e, + 0x4f,0x50,0x4e,0x4d,0x47,0x41,0x42,0x3f,0x40,0x43,0x43,0x46,0x45,0x41,0x44,0x42, + 0x76,0x59,0x4d,0x61,0x64,0x53,0x5c,0x7b,0x84,0x7a,0x6c,0x5d,0x5b,0x55,0x5e,0x79, + 0x82,0x85,0x84,0x7f,0x79,0x41,0x63,0x8c,0x7f,0x7d,0x79,0x6c,0x76,0x80,0x6b,0x59, + 0x61,0x6c,0x6e,0x73,0x64,0x5f,0x7e,0x74,0x73,0x74,0x74,0x74,0x73,0x73,0x6f,0x72, + 0x7d,0x90,0xa2,0xab,0xb4,0xba,0xbf,0xc1,0xc3,0xc3,0xbf,0xbc,0xba,0xb9,0xba,0xba, + 0xbd,0xc0,0xc1,0xc2,0xc3,0xc3,0xbe,0xb8,0xb3,0xab,0x9f,0x8e,0x76,0x60,0x50,0x49, + 0x45,0x48,0x49,0x4a,0x4a,0x48,0x48,0x49,0x44,0x41,0x42,0x41,0x40,0x41,0x43,0x45, + 0x47,0x4a,0x50,0x50,0x4f,0x4e,0x4e,0x4b,0x46,0x43,0x4a,0x4c,0x50,0x52,0x51,0x4d, + 0x4d,0x4d,0x4c,0x4c,0x48,0x45,0x42,0x3f,0x3c,0x39,0x38,0x39,0x3b,0x39,0x3a,0x3a, + 0x86,0x7e,0x71,0x70,0x7d,0x74,0x5e,0x5f,0x71,0x83,0x8a,0x88,0x83,0x7e,0x7e,0x7f, + 0x82,0x86,0x85,0x81,0x7f,0x4e,0x2a,0x74,0x80,0x7d,0x65,0x68,0x5f,0x5a,0x54,0x55, + 0x5e,0x74,0x6d,0x7c,0x5b,0x73,0x92,0x74,0x74,0x73,0x73,0x73,0x76,0x77,0x75,0x71, + 0x71,0x78,0x89,0x98,0xa5,0xab,0xb4,0xb9,0xbd,0xbe,0xbc,0xbd,0xbd,0xbe,0xbe,0xbf, + 0xbf,0xbf,0xc2,0xc2,0xc2,0xc0,0xbd,0xb7,0xb0,0xa7,0x9c,0x88,0x72,0x5f,0x53,0x4f, + 0x50,0x4d,0x4f,0x4e,0x4b,0x47,0x49,0x49,0x47,0x44,0x42,0x41,0x40,0x42,0x44,0x47, + 0x4b,0x4e,0x51,0x51,0x53,0x51,0x4e,0x4b,0x45,0x49,0x48,0x4c,0x51,0x54,0x52,0x4e, + 0x4e,0x4c,0x4b,0x49,0x47,0x45,0x3e,0x3b,0x39,0x36,0x38,0x39,0x37,0x37,0x36,0x32, + 0x73,0x7f,0x85,0x8b,0x84,0x88,0x7e,0x6a,0x64,0x74,0x84,0x8c,0x91,0x8f,0x8d,0x8a, + 0x89,0x86,0x85,0x82,0x7f,0x67,0x1d,0x48,0x84,0x82,0x63,0x59,0x54,0x55,0x53,0x57, + 0x55,0x66,0x70,0x82,0x59,0x7f,0x92,0x72,0x73,0x73,0x74,0x74,0x74,0x74,0x74,0x73, + 0x73,0x72,0x77,0x85,0x93,0x9f,0xa8,0xaf,0xb4,0xb9,0xbc,0xbb,0xbc,0xbc,0xbc,0xbd, + 0xbe,0xbf,0xc0,0xc0,0xc1,0xbf,0xbc,0xb4,0xac,0xa4,0x94,0x81,0x6e,0x60,0x54,0x55, + 0x54,0x54,0x52,0x51,0x4f,0x4b,0x4b,0x4c,0x4b,0x47,0x41,0x41,0x40,0x42,0x46,0x4b, + 0x4b,0x4d,0x50,0x51,0x52,0x51,0x4c,0x48,0x45,0x48,0x46,0x47,0x4c,0x51,0x50,0x4e, + 0x4f,0x4c,0x49,0x48,0x44,0x3f,0x39,0x39,0x36,0x34,0x35,0x36,0x37,0x39,0x37,0x2f, + 0x51,0x6d,0x86,0x89,0x86,0x88,0x87,0x84,0x80,0x7e,0x87,0x8c,0x8c,0x8d,0x8c,0x88, + 0x86,0x86,0x85,0x83,0x7e,0x74,0x2d,0x1e,0x6f,0x81,0x63,0x58,0x5d,0x60,0x5e,0x56, + 0x5d,0x76,0x89,0x73,0x64,0x91,0x93,0x72,0x74,0x73,0x74,0x74,0x74,0x74,0x74,0x75, + 0x75,0x76,0x74,0x77,0x7e,0x8a,0x95,0x9f,0xa7,0xaf,0xb3,0xb6,0xb9,0xb9,0xba,0xbc, + 0xbd,0xbd,0xbf,0xbf,0xbd,0xbd,0xba,0xb1,0xa7,0x9d,0x8c,0x79,0x6a,0x5d,0x56,0x58, + 0x56,0x54,0x52,0x51,0x4f,0x50,0x51,0x51,0x4d,0x4b,0x44,0x40,0x42,0x43,0x46,0x4a, + 0x4a,0x4c,0x4f,0x50,0x50,0x4e,0x4a,0x46,0x41,0x42,0x42,0x43,0x48,0x4d,0x4c,0x4e, + 0x50,0x4f,0x4c,0x4a,0x46,0x3e,0x36,0x35,0x31,0x31,0x33,0x34,0x35,0x36,0x35,0x2e, + 0x5a,0x61,0x84,0x85,0x88,0x89,0x86,0x83,0x86,0x87,0x86,0x87,0x8a,0x8a,0x86,0x85, + 0x84,0x84,0x84,0x81,0x7e,0x7c,0x47,0x16,0x3c,0x7d,0x70,0x5a,0x66,0x5f,0x71,0x58, + 0x68,0x8b,0x7c,0x6e,0x71,0xa2,0x93,0x71,0x73,0x74,0x74,0x75,0x75,0x75,0x75,0x75, + 0x75,0x75,0x76,0x75,0x79,0x7e,0x86,0x91,0x9b,0xa4,0xad,0xb3,0xb6,0xb7,0xb9,0xbb, + 0xbb,0xbc,0xbe,0xbf,0xbc,0xba,0xb7,0xad,0xa2,0x94,0x82,0x70,0x63,0x5c,0x58,0x59, + 0x56,0x54,0x52,0x51,0x53,0x57,0x57,0x54,0x53,0x4d,0x48,0x45,0x45,0x45,0x47,0x49, + 0x49,0x4c,0x4e,0x4e,0x4d,0x4b,0x48,0x43,0x3e,0x3f,0x3e,0x3d,0x43,0x48,0x4b,0x4d, + 0x4f,0x50,0x4e,0x4e,0x48,0x40,0x36,0x33,0x30,0x2d,0x2f,0x31,0x30,0x2e,0x2d,0x2a, + 0x78,0x72,0x6f,0x7a,0x81,0x85,0x85,0x81,0x84,0x84,0x85,0x84,0x83,0x83,0x82,0x83, + 0x81,0x81,0x82,0x80,0x7d,0x7e,0x67,0x1e,0x18,0x6c,0x7d,0x5c,0x5b,0x5e,0x6f,0x6a, + 0x68,0x71,0x65,0x64,0x74,0xb9,0x92,0x72,0x74,0x75,0x76,0x76,0x75,0x75,0x75,0x75, + 0x75,0x75,0x77,0x77,0x78,0x79,0x7c,0x85,0x8e,0x9b,0xa6,0xad,0xb4,0xb5,0xb6,0xb8, + 0xba,0xbc,0xbc,0xbc,0xba,0xb7,0xb2,0xa5,0x99,0x89,0x76,0x67,0x5b,0x5b,0x5a,0x59, + 0x57,0x56,0x55,0x56,0x58,0x5d,0x5b,0x5c,0x59,0x54,0x4e,0x4a,0x47,0x46,0x46,0x48, + 0x48,0x4a,0x4d,0x4c,0x4b,0x49,0x44,0x3e,0x39,0x38,0x38,0x39,0x3f,0x46,0x4b,0x50, + 0x52,0x52,0x4e,0x4f,0x49,0x42,0x38,0x31,0x2d,0x2a,0x2b,0x2e,0x2c,0x2b,0x26,0x24, + 0x7f,0x7f,0x7c,0x7d,0x83,0x85,0x83,0x82,0x83,0x83,0x85,0x83,0x82,0x81,0x7f,0x84, + 0x82,0x82,0x82,0x82,0x81,0x84,0x7c,0x23,0x14,0x39,0x85,0x72,0x53,0x54,0x60,0x63, + 0x5e,0x63,0x58,0x67,0x7e,0xc7,0x89,0x72,0x74,0x74,0x75,0x75,0x74,0x75,0x75,0x75, + 0x75,0x75,0x79,0x78,0x7a,0x7a,0x7b,0x7c,0x84,0x94,0xa0,0xab,0xb0,0xb3,0xb6,0xb8, + 0xb8,0xba,0xba,0xba,0xb9,0xb4,0xac,0x9c,0x8b,0x7a,0x67,0x5d,0x59,0x57,0x55,0x55, + 0x56,0x55,0x57,0x59,0x5c,0x61,0x61,0x5f,0x5e,0x59,0x52,0x4e,0x4a,0x47,0x48,0x49, + 0x4c,0x4d,0x4b,0x48,0x49,0x47,0x40,0x3a,0x35,0x33,0x32,0x35,0x38,0x43,0x45,0x4d, + 0x4e,0x4f,0x4e,0x4d,0x4b,0x43,0x3b,0x31,0x2d,0x29,0x29,0x2a,0x29,0x28,0x24,0x21, + 0x7d,0x7e,0x7e,0x7d,0x81,0x82,0x82,0x84,0x84,0x85,0x81,0x81,0x80,0x81,0x82,0x81, + 0x82,0x82,0x85,0x85,0x89,0x8c,0x85,0x29,0x13,0x14,0x6b,0x85,0x5b,0x55,0x5c,0x60, + 0x60,0x5f,0x59,0x73,0x84,0xd4,0x82,0x75,0x74,0x76,0x76,0x75,0x75,0x75,0x75,0x75, + 0x76,0x76,0x77,0x77,0x7a,0x79,0x7a,0x7f,0x88,0x93,0x9d,0xa3,0xaf,0xb1,0xb4,0xb5, + 0xb8,0xb9,0xb9,0xb8,0xb8,0xb1,0xa1,0x8e,0x7c,0x6b,0x5b,0x55,0x57,0x55,0x53,0x51, + 0x53,0x55,0x57,0x59,0x5f,0x63,0x63,0x62,0x61,0x5d,0x55,0x4f,0x4e,0x4d,0x4d,0x4c, + 0x4d,0x50,0x4f,0x4a,0x49,0x45,0x3c,0x38,0x35,0x31,0x2f,0x32,0x3b,0x41,0x42,0x47, + 0x4a,0x49,0x4d,0x4d,0x49,0x44,0x3d,0x36,0x2f,0x2a,0x29,0x25,0x26,0x24,0x21,0x1f, + 0x7e,0x7e,0x7e,0x7e,0x7f,0x7d,0x80,0x81,0x82,0x83,0x82,0x81,0x80,0x80,0x7f,0x80, + 0x82,0x84,0x85,0x8a,0x8f,0x93,0x89,0x24,0x12,0x0d,0x3a,0x88,0x72,0x5e,0x5c,0x5d, + 0x5e,0x5a,0x61,0x84,0x99,0xd9,0x7c,0x75,0x73,0x75,0x76,0x76,0x76,0x76,0x75,0x75, + 0x75,0x75,0x76,0x77,0x76,0x76,0x79,0x82,0x8b,0x93,0xa0,0xa6,0xaa,0xae,0xb1,0xb2, + 0xb6,0xb7,0xb8,0xb8,0xb4,0xaa,0x98,0x82,0x6d,0x5e,0x53,0x52,0x53,0x52,0x53,0x52, + 0x54,0x56,0x57,0x59,0x62,0x64,0x62,0x63,0x63,0x5f,0x57,0x54,0x55,0x54,0x54,0x52, + 0x51,0x54,0x51,0x4e,0x4a,0x44,0x3e,0x39,0x33,0x30,0x30,0x36,0x3e,0x40,0x41,0x44, + 0x44,0x46,0x48,0x49,0x49,0x46,0x40,0x3a,0x35,0x2e,0x28,0x25,0x24,0x23,0x21,0x21, + 0x7f,0x7e,0x7f,0x7e,0x7e,0x7e,0x80,0x81,0x81,0x82,0x80,0x7f,0x7f,0x80,0x81,0x81, + 0x84,0x87,0x8d,0x92,0x97,0x98,0x62,0x18,0x11,0x0c,0x1a,0x71,0x87,0x5f,0x64,0x60, + 0x5d,0x58,0x78,0x8d,0xb6,0xd8,0x78,0x77,0x77,0x77,0x77,0x76,0x76,0x76,0x75,0x75, + 0x76,0x76,0x77,0x76,0x74,0x75,0x7b,0x88,0x90,0x99,0xa3,0xa9,0xab,0xad,0xb0,0xb1, + 0xb4,0xb5,0xb6,0xb7,0xb0,0xa3,0x8c,0x75,0x60,0x56,0x55,0x56,0x57,0x55,0x54,0x55, + 0x56,0x58,0x5b,0x5e,0x63,0x67,0x67,0x65,0x65,0x62,0x5f,0x5c,0x5b,0x59,0x59,0x58, + 0x5a,0x5a,0x56,0x54,0x4e,0x46,0x3d,0x37,0x33,0x33,0x34,0x3b,0x40,0x41,0x42,0x3f, + 0x42,0x46,0x47,0x49,0x46,0x42,0x44,0x39,0x37,0x31,0x2b,0x27,0x25,0x24,0x21,0x20, + 0x7f,0x82,0x84,0x82,0x82,0x83,0x7f,0x80,0x80,0x80,0x7f,0x7e,0x7f,0x7f,0x82,0x83, + 0x87,0x8e,0x97,0x99,0x9c,0x6a,0x14,0x10,0x0d,0x0a,0x0f,0x3b,0x87,0x66,0x5f,0x5f, + 0x5e,0x5e,0x8a,0x90,0xd2,0xcd,0x77,0x77,0x77,0x77,0x76,0x77,0x77,0x76,0x75,0x75, + 0x75,0x77,0x76,0x75,0x72,0x76,0x81,0x8f,0x95,0x9f,0xa6,0xa8,0xa8,0xad,0xb0,0xb1, + 0xb2,0xb4,0xb4,0xb4,0xac,0x9b,0x81,0x6b,0x58,0x53,0x57,0x58,0x59,0x57,0x57,0x57, + 0x56,0x56,0x5b,0x60,0x63,0x65,0x67,0x6c,0x6c,0x69,0x64,0x63,0x5f,0x5d,0x5d,0x5f, + 0x61,0x62,0x5d,0x58,0x4c,0x43,0x3d,0x39,0x37,0x3b,0x3d,0x43,0x46,0x46,0x42,0x41, + 0x41,0x40,0x43,0x46,0x46,0x46,0x43,0x3c,0x38,0x33,0x2e,0x27,0x25,0x24,0x23,0x22, + 0x80,0x83,0x84,0x81,0x81,0x82,0x7f,0x81,0x7e,0x7f,0x7e,0x7e,0x7d,0x82,0x85,0x89, + 0x8e,0x97,0x9b,0x9e,0x8e,0x56,0x30,0x18,0x15,0x0b,0x0e,0x16,0x6c,0x7a,0x5a,0x5f, + 0x62,0x6c,0x8a,0x96,0xe4,0xbc,0x77,0x77,0x77,0x77,0x79,0x79,0x77,0x76,0x77,0x77, + 0x76,0x76,0x74,0x72,0x72,0x7b,0x86,0x95,0x9d,0xa3,0xa7,0xa8,0xa8,0xad,0xaf,0xb0, + 0xb1,0xb3,0xb3,0xb1,0xa8,0x97,0x7d,0x69,0x57,0x54,0x55,0x58,0x5b,0x58,0x57,0x57, + 0x58,0x57,0x5c,0x61,0x63,0x67,0x67,0x6c,0x6c,0x6b,0x68,0x65,0x61,0x61,0x62,0x63, + 0x67,0x68,0x61,0x55,0x49,0x43,0x3f,0x3a,0x3c,0x40,0x45,0x49,0x48,0x47,0x43,0x42, + 0x41,0x41,0x44,0x45,0x46,0x47,0x44,0x41,0x3c,0x37,0x34,0x2b,0x28,0x26,0x24,0x21, + 0x82,0x84,0x84,0x82,0x81,0x7f,0x7f,0x80,0x7f,0x7d,0x7d,0x7e,0x7f,0x82,0x89,0x8e, + 0x99,0x9f,0xa0,0x93,0x7b,0x73,0x62,0x27,0x24,0x14,0x0f,0x12,0x3d,0x82,0x64,0x57, + 0x5d,0x7a,0x88,0xa9,0xea,0xa6,0x77,0x76,0x78,0x78,0x78,0x77,0x77,0x77,0x75,0x75, + 0x74,0x72,0x71,0x6e,0x74,0x7f,0x8b,0x97,0xa3,0xa4,0xa5,0xa8,0xa9,0xac,0xac,0xae, + 0xb0,0xb1,0xb2,0xb0,0xa5,0x90,0x7b,0x6c,0x5b,0x59,0x58,0x5a,0x57,0x54,0x55,0x55, + 0x58,0x58,0x5d,0x60,0x64,0x69,0x68,0x6c,0x6b,0x6c,0x69,0x64,0x5f,0x61,0x62,0x65, + 0x6a,0x66,0x5f,0x55,0x4e,0x44,0x3c,0x39,0x3d,0x42,0x48,0x4a,0x49,0x47,0x43,0x44, + 0x43,0x43,0x43,0x45,0x43,0x44,0x46,0x44,0x42,0x40,0x39,0x31,0x2d,0x26,0x27,0x22, + 0x85,0x84,0x7f,0x7f,0x7f,0x81,0x7e,0x7e,0x7d,0x7d,0x7e,0x80,0x82,0x89,0x91,0x98, + 0x9e,0xa2,0x95,0x7d,0x78,0x77,0x6f,0x28,0x19,0x1a,0x18,0x0f,0x18,0x69,0x76,0x58, + 0x65,0x82,0x85,0xc7,0xe9,0x91,0x75,0x75,0x77,0x78,0x78,0x77,0x77,0x77,0x77,0x74, + 0x72,0x6e,0x6a,0x6b,0x76,0x84,0x91,0x9c,0xa4,0xa4,0xa6,0xa7,0xa8,0xa8,0xa9,0xab, + 0xac,0xad,0xaf,0xac,0xa0,0x8f,0x7e,0x6e,0x61,0x5e,0x5b,0x5c,0x58,0x56,0x56,0x58, + 0x5a,0x5a,0x5e,0x61,0x66,0x69,0x69,0x6c,0x6c,0x6a,0x66,0x60,0x5c,0x5c,0x5d,0x60, + 0x64,0x5f,0x58,0x54,0x4a,0x3f,0x39,0x36,0x38,0x40,0x48,0x4b,0x48,0x47,0x43,0x42, + 0x43,0x44,0x46,0x48,0x47,0x48,0x48,0x48,0x49,0x45,0x3d,0x38,0x32,0x2b,0x27,0x25, + 0x7b,0x7d,0x7e,0x7f,0x81,0x80,0x7e,0x7e,0x7e,0x7e,0x7f,0x82,0x89,0x93,0x9a,0x9e, + 0xa0,0x93,0x80,0x7d,0x7a,0x7a,0x71,0x2f,0x15,0x10,0x16,0x16,0x14,0x3e,0x76,0x64, + 0x73,0x89,0x97,0xe4,0xda,0x79,0x71,0x71,0x75,0x75,0x77,0x77,0x77,0x77,0x77,0x73, + 0x6f,0x68,0x67,0x6e,0x7c,0x8a,0x96,0x9f,0xa2,0xa2,0xa2,0xa2,0xa2,0xa3,0xa5,0xa7, + 0xa9,0xab,0xac,0xa7,0x9e,0x90,0x80,0x6f,0x66,0x62,0x60,0x5e,0x58,0x58,0x58,0x5a, + 0x5e,0x61,0x64,0x65,0x68,0x69,0x6b,0x6c,0x6c,0x68,0x62,0x5c,0x56,0x56,0x56,0x58, + 0x59,0x55,0x51,0x4a,0x40,0x38,0x34,0x33,0x31,0x3b,0x42,0x48,0x47,0x45,0x42,0x41, + 0x43,0x46,0x46,0x47,0x48,0x49,0x48,0x4b,0x4a,0x44,0x40,0x3c,0x38,0x2e,0x2c,0x28, + 0x7d,0x81,0x81,0x7f,0x7a,0x7d,0x7c,0x7d,0x7f,0x7e,0x83,0x89,0x93,0x9b,0x9f,0xa1, + 0x91,0x82,0x7d,0x7f,0x7e,0x7a,0x72,0x2e,0x18,0x0e,0x13,0x10,0x0f,0x1e,0x63,0x72, + 0x84,0x8a,0xb6,0xeb,0xc0,0x6e,0x70,0x70,0x72,0x73,0x74,0x76,0x75,0x76,0x73,0x6e, + 0x6a,0x65,0x64,0x6f,0x81,0x8d,0x99,0x9d,0x9d,0x9d,0x9e,0x9e,0x9e,0x9f,0xa0,0xa3, + 0xa5,0xa8,0xa8,0xa3,0x9a,0x8f,0x81,0x72,0x67,0x63,0x5e,0x59,0x55,0x53,0x56,0x5c, + 0x5e,0x61,0x64,0x69,0x6a,0x6a,0x6b,0x6d,0x6a,0x61,0x5b,0x56,0x51,0x4f,0x4f,0x4e, + 0x4f,0x49,0x45,0x3c,0x34,0x2c,0x2b,0x2d,0x2d,0x32,0x39,0x3f,0x43,0x44,0x41,0x42, + 0x45,0x47,0x48,0x4a,0x4a,0x4b,0x4a,0x49,0x47,0x43,0x42,0x3e,0x39,0x32,0x2f,0x2a, + 0x80,0x7f,0x7d,0x7b,0x7b,0x7b,0x7c,0x7f,0x81,0x83,0x8c,0x96,0x9b,0x9e,0x9e,0x91, + 0x82,0x81,0x7f,0x7e,0x7e,0x7b,0x75,0x34,0x16,0x10,0x12,0x0d,0x0d,0x10,0x49,0x6a, + 0x8a,0x92,0xd8,0xeb,0x9d,0x6b,0x6f,0x6f,0x70,0x70,0x72,0x74,0x74,0x72,0x6f,0x68, + 0x63,0x5f,0x65,0x74,0x83,0x90,0x98,0x99,0x99,0x99,0x99,0x9a,0x9b,0x9d,0x9f,0xa1, + 0xa2,0xa5,0xa5,0x9f,0x99,0x8d,0x82,0x78,0x72,0x6a,0x65,0x5b,0x58,0x56,0x57,0x58, + 0x5c,0x64,0x65,0x6b,0x6d,0x6a,0x67,0x66,0x62,0x5a,0x57,0x52,0x4c,0x47,0x45,0x42, + 0x40,0x3e,0x37,0x2f,0x2a,0x26,0x27,0x26,0x26,0x2b,0x33,0x3a,0x3f,0x43,0x43,0x46, + 0x49,0x4a,0x4d,0x4d,0x4d,0x4d,0x4a,0x47,0x46,0x43,0x42,0x3f,0x3b,0x33,0x31,0x2f, + 0x7a,0x7a,0x7a,0x7a,0x7a,0x7c,0x7e,0x80,0x83,0x8c,0x98,0x9c,0xa1,0xa0,0x92,0x85, + 0x83,0x83,0x80,0x7f,0x7f,0x7c,0x76,0x37,0x18,0x0f,0x11,0x0f,0x0f,0x0d,0x33,0x72, + 0x8c,0xa9,0xe6,0xe1,0x7e,0x6b,0x6e,0x6e,0x6e,0x70,0x71,0x73,0x71,0x70,0x69,0x62, + 0x5e,0x5d,0x68,0x74,0x83,0x8f,0x94,0x95,0x94,0x95,0x98,0x99,0x9b,0x9d,0xa0,0xa2, + 0xa6,0xa2,0xb7,0xdd,0xc7,0xcf,0xd4,0xd7,0xdb,0xd5,0xd1,0xcd,0xca,0xc6,0xc0,0xb9, + 0xae,0xa5,0x9e,0x98,0x91,0x8a,0x82,0x7a,0x75,0x6f,0x68,0x60,0x56,0x50,0x48,0x3f, + 0x2f,0x2e,0x2b,0x24,0x25,0x24,0x24,0x22,0x21,0x27,0x31,0x3a,0x41,0x44,0x46,0x49, + 0x4b,0x4d,0x51,0x54,0x52,0x4f,0x4a,0x48,0x45,0x41,0x40,0x3e,0x3b,0x35,0x34,0x31, + 0x7a,0x7a,0x7a,0x7a,0x7b,0x7d,0x7f,0x87,0x90,0x9b,0x9e,0xa1,0x9b,0x90,0x86,0x85, + 0x85,0x84,0x81,0x80,0x7e,0x7c,0x76,0x3a,0x19,0x0f,0x10,0x0f,0x0f,0x12,0x3f,0x84, + 0x91,0xd3,0xeb,0xcc,0x6e,0x6d,0x6c,0x6c,0x6d,0x6e,0x70,0x71,0x71,0x6b,0x65,0x5d, + 0x5a,0x5c,0x69,0x77,0x83,0x8c,0x91,0x92,0x94,0x95,0x99,0x9b,0x9d,0xa1,0xa3,0xa5, + 0xa3,0x9f,0x91,0x2b,0x28,0x27,0x27,0x2a,0x2e,0x2f,0x34,0x41,0x49,0x53,0x5f,0x63, + 0x69,0x77,0x8a,0x95,0x9a,0xa5,0xab,0xb1,0xb4,0xba,0xc0,0xc3,0xcb,0xd4,0xd6,0xcb, + 0xb1,0x62,0x27,0x24,0x23,0x23,0x21,0x22,0x24,0x29,0x33,0x3c,0x3f,0x41,0x45,0x48, + 0x4b,0x4d,0x53,0x58,0x56,0x52,0x4d,0x46,0x44,0x3f,0x3f,0x3e,0x3b,0x38,0x36,0x37, + 0x7a,0x7a,0x79,0x7b,0x7c,0x7e,0x85,0x93,0x9b,0x9f,0xa0,0x98,0x8c,0x86,0x85,0x85, + 0x84,0x83,0x83,0x81,0x80,0x7f,0x7a,0x3e,0x19,0x10,0x0e,0x0f,0x11,0x19,0x61,0x8a, + 0xac,0xe5,0xea,0xac,0x6d,0x6c,0x6a,0x6b,0x6b,0x6d,0x6e,0x70,0x6e,0x67,0x60,0x59, + 0x58,0x5e,0x6c,0x79,0x82,0x89,0x90,0x92,0x92,0x95,0x9a,0x9e,0xa0,0xa4,0xa5,0xa8, + 0x96,0xc5,0x3a,0x39,0x6b,0x6b,0x61,0x5a,0x52,0x47,0x43,0x3b,0x37,0x33,0x30,0x2d, + 0x2f,0x2f,0x30,0x32,0x34,0x35,0x32,0x39,0x37,0x3a,0x3c,0x3e,0x40,0x41,0x43,0x43, + 0x41,0x53,0x78,0x27,0x26,0x24,0x24,0x24,0x25,0x2b,0x31,0x3a,0x3a,0x3d,0x3f,0x44, + 0x48,0x49,0x52,0x57,0x55,0x52,0x4d,0x47,0x44,0x3f,0x40,0x40,0x3e,0x3b,0x39,0x36, + 0x79,0x7a,0x7a,0x7c,0x7c,0x85,0x95,0x9e,0x9b,0xa1,0x98,0x8b,0x87,0x86,0x87,0x85, + 0x85,0x85,0x83,0x83,0x82,0x80,0x7c,0x41,0x18,0x11,0x0d,0x0d,0x12,0x3d,0x7f,0x8d, + 0xd3,0xe8,0xe2,0x88,0x6b,0x6a,0x6a,0x6a,0x6a,0x6b,0x6e,0x6f,0x6c,0x63,0x5d,0x57, + 0x58,0x65,0x71,0x7d,0x85,0x8b,0x8e,0x92,0x95,0x97,0x9d,0xa0,0xa3,0xa6,0xa7,0xa6, + 0x8c,0xc2,0x37,0x4a,0xc9,0xd0,0xd1,0xdc,0xdd,0xdc,0xdf,0xde,0xdf,0xdd,0xd8,0xd5, + 0xce,0xc7,0xc0,0xbb,0xb1,0xaa,0xa2,0x99,0x96,0x8e,0x88,0x83,0x7c,0x78,0x72,0x69, + 0x3c,0x2a,0x8b,0x2a,0x2e,0x2b,0x29,0x28,0x29,0x2b,0x31,0x36,0x37,0x38,0x3c,0x40, + 0x41,0x48,0x4e,0x50,0x53,0x54,0x52,0x4b,0x48,0x43,0x41,0x41,0x40,0x3e,0x3e,0x39, + 0x7b,0x7c,0x7b,0x7c,0x84,0x93,0x9d,0x9f,0x9c,0x96,0x8b,0x89,0x87,0x86,0x86,0x86, + 0x87,0x88,0x84,0x84,0x81,0x81,0x7b,0x44,0x19,0x12,0x0d,0x0d,0x21,0x6b,0x82,0xad, + 0xe8,0xe7,0xcb,0x6f,0x6a,0x68,0x69,0x69,0x69,0x6b,0x6d,0x6e,0x6a,0x62,0x5b,0x58, + 0x5c,0x69,0x76,0x80,0x88,0x8e,0x91,0x94,0x96,0x98,0x9e,0xa1,0xa4,0xa4,0xa5,0xa5, + 0x7f,0xc1,0x3a,0x49,0xc1,0xca,0xcc,0xcc,0xd1,0xd4,0xd2,0xd3,0xd3,0xd4,0xd5,0xd5, + 0xd5,0xd6,0xd5,0xd6,0xd4,0xd5,0xd6,0xd6,0xd5,0xd7,0xd6,0xd7,0xd4,0xd3,0xd4,0xdc, + 0x3e,0x2f,0x8c,0x3b,0x39,0x35,0x33,0x2f,0x2f,0x2e,0x30,0x34,0x35,0x36,0x36,0x39, + 0x3c,0x42,0x46,0x49,0x51,0x54,0x51,0x4b,0x49,0x45,0x43,0x41,0x41,0x41,0x3f,0x3b, + 0x7c,0x7e,0x7f,0x86,0x98,0x9c,0x9b,0x99,0x90,0x89,0x89,0x88,0x86,0x86,0x87,0x89, + 0x88,0x88,0x84,0x85,0x82,0x7f,0x7d,0x48,0x18,0x12,0x0d,0x12,0x47,0x7f,0x92,0xd8, + 0xe8,0xe5,0x9f,0x6a,0x6b,0x6a,0x69,0x69,0x68,0x6a,0x6c,0x6c,0x67,0x60,0x5a,0x58, + 0x61,0x71,0x7e,0x87,0x8e,0x91,0x94,0x95,0x95,0x9e,0xa7,0xa8,0xa8,0xa7,0xac,0xaa, + 0x73,0x6f,0x37,0x4c,0xbf,0xcb,0xcf,0xce,0xd0,0xd2,0xd3,0xd4,0xd5,0xd5,0xd5,0xd4, + 0xd5,0xd6,0xd6,0xd5,0xd6,0xd5,0xd6,0xd6,0xd6,0xd7,0xd7,0xd7,0xd7,0xd6,0xd5,0xe1, + 0x44,0x33,0x8c,0x43,0x46,0x40,0x3c,0x38,0x35,0x34,0x33,0x32,0x32,0x33,0x33,0x34, + 0x38,0x3b,0x3e,0x44,0x4a,0x4f,0x4c,0x4b,0x4d,0x4a,0x46,0x45,0x43,0x42,0x40,0x3c, + 0x7d,0x80,0x87,0x99,0x9b,0x9d,0x97,0x8e,0x89,0x89,0x87,0x87,0x88,0x88,0x88,0x86, + 0x87,0x87,0x85,0x85,0x81,0x7f,0x77,0x4b,0x19,0x11,0x0c,0x23,0x6b,0x89,0xb5,0xe6, + 0xe6,0xd8,0x7e,0x70,0x6d,0x6c,0x6a,0x68,0x67,0x68,0x69,0x69,0x65,0x5e,0x59,0x57, + 0x69,0x79,0x83,0x8c,0x92,0x94,0x93,0x93,0x9e,0xad,0xb1,0xaf,0xae,0xb1,0xb6,0xb6, + 0x55,0x1f,0x2f,0x50,0xc7,0xd1,0xce,0xd1,0xd3,0xd4,0xd3,0xd4,0xd4,0xd4,0xd4,0xd3, + 0xd4,0xd4,0xd4,0xd0,0xd5,0xd5,0xd6,0xd7,0xd7,0xd6,0xd6,0xd6,0xd7,0xd6,0xd5,0xd5, + 0x35,0x2c,0x87,0x54,0x51,0x4b,0x48,0x42,0x3c,0x38,0x36,0x35,0x33,0x34,0x36,0x36, + 0x37,0x39,0x3b,0x3e,0x42,0x48,0x47,0x48,0x4a,0x48,0x45,0x43,0x42,0x40,0x40,0x3f, + 0x86,0x94,0xa2,0xa3,0xa3,0x9d,0x93,0x90,0x91,0x90,0x90,0x92,0x91,0x90,0x8f,0x8e, + 0x8d,0x8c,0x89,0x8c,0x87,0x83,0x7d,0x53,0x14,0x0a,0x0f,0x4c,0x85,0x9e,0xe2,0xf0, + 0xf0,0xba,0x75,0x72,0x74,0x72,0x6f,0x6e,0x67,0x6a,0x6b,0x68,0x63,0x5c,0x5b,0x5f, + 0x70,0x80,0x8b,0x91,0x94,0x95,0x93,0x9d,0xb1,0xbb,0xbb,0xb9,0xb6,0xc5,0xce,0xc4, + 0x30,0x1f,0x2c,0x51,0xc9,0xcc,0xd1,0xd1,0xd2,0xd4,0xd2,0xd3,0xd4,0xd5,0xd5,0xd6, + 0xd5,0xd4,0xd5,0xd6,0xd5,0xd6,0xd5,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd5,0xd6,0xcf, + 0x38,0x26,0x15,0x60,0x5c,0x56,0x52,0x4a,0x42,0x3d,0x3a,0x37,0x36,0x36,0x39,0x3a, + 0x39,0x3b,0x3c,0x3a,0x3f,0x43,0x44,0x46,0x45,0x44,0x44,0x41,0x3f,0x3b,0x3b,0x3c, + 0x36,0x38,0x33,0x3a,0x37,0x3b,0x3e,0x3e,0x3d,0x40,0x41,0x41,0x45,0x47,0x46,0x4c, + 0x4b,0x4a,0x4c,0x4b,0x4d,0x51,0x50,0x4f,0x40,0x3d,0x3d,0x51,0x5a,0x6f,0x7f,0x81, + 0x83,0x68,0x5b,0x5a,0x59,0x5d,0x65,0x62,0x64,0x5d,0x65,0x60,0x5d,0x56,0x57,0x5b, + 0x66,0x70,0x7a,0x7e,0x80,0x82,0x8b,0x99,0xa9,0xa9,0xa4,0x98,0x97,0x76,0x26,0x01, + 0x11,0x21,0x26,0x51,0xc9,0xcf,0xcc,0xcf,0xd1,0xd2,0xd3,0xd2,0xd2,0xd4,0xd4,0xd5, + 0xd5,0xd4,0xd3,0xd3,0xd3,0xd4,0xd6,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xe1, + 0x34,0x19,0x13,0x6a,0x62,0x5e,0x58,0x54,0x4c,0x41,0x3d,0x38,0x35,0x39,0x3b,0x3c, + 0x3c,0x3f,0x3e,0x3c,0x3f,0x42,0x45,0x44,0x43,0x42,0x41,0x40,0x3c,0x36,0x36,0x36, + 0x60,0x60,0x5e,0x5f,0x5f,0x61,0x6a,0x5f,0x61,0x64,0x60,0x5e,0x58,0x5a,0x5a,0x5c, + 0x5e,0x61,0x5e,0x5d,0x59,0x61,0x62,0x64,0x5e,0x5b,0x63,0x5d,0x5c,0x5b,0x56,0x51, + 0x50,0x51,0x57,0x4e,0x5d,0x6f,0xc4,0xd0,0xbe,0xce,0xc7,0x7a,0x59,0x58,0x58,0x54, + 0x50,0x52,0x4e,0x47,0x3a,0x39,0x3a,0x3c,0x3a,0x1f,0x12,0x14,0x12,0x13,0x12,0x12, + 0x0d,0x18,0x21,0x51,0xcc,0xcf,0xcd,0xce,0xd0,0xd2,0xd0,0xd1,0xd3,0xd5,0xd5,0xd5, + 0xd4,0xd4,0xd4,0xd4,0xd5,0xd4,0xd6,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd4,0xe1, + 0x29,0x15,0x14,0x6c,0x68,0x63,0x5e,0x59,0x51,0x49,0x41,0x3d,0x3a,0x3d,0x3e,0x40, + 0x44,0x47,0x44,0x42,0x43,0x43,0x43,0x43,0x46,0x44,0x3f,0x3b,0x38,0x34,0x33,0x33, + 0x9a,0x9f,0x93,0x9f,0x8d,0x9e,0x8c,0x96,0x93,0x8d,0x99,0x95,0x94,0x91,0x94,0x99, + 0x95,0x9a,0x97,0x9b,0x9c,0x9f,0xa1,0x9e,0xa3,0xa3,0xa2,0xa4,0xa1,0x9e,0x93,0x95, + 0x96,0x91,0x94,0x8f,0x99,0xa9,0xfa,0xf7,0xf6,0xf6,0xf9,0xb6,0x9b,0x9c,0x9c,0x99, + 0x97,0x78,0x71,0x52,0x4f,0x49,0x49,0x50,0x4f,0x1b,0x16,0x14,0x12,0x15,0x16,0x10, + 0x32,0x8b,0x2f,0x4f,0xc6,0xcd,0xcf,0xd0,0xd1,0xd0,0xd3,0xd3,0xd3,0xd4,0xd1,0xd1, + 0xd5,0xd4,0xd4,0xd4,0xd6,0xd6,0xd6,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xe1, + 0x31,0x15,0x12,0x6c,0x68,0x67,0x61,0x5a,0x53,0x49,0x46,0x40,0x3d,0x3f,0x43,0x46, + 0x4b,0x4d,0x4c,0x49,0x48,0x47,0x43,0x44,0x44,0x43,0x3e,0x3b,0x37,0x33,0x31,0x31, + 0x8a,0x8b,0x94,0x8f,0x92,0x9b,0x94,0x95,0x93,0x94,0x96,0x98,0x9f,0xa4,0xa1,0xa6, + 0xa8,0xa5,0xa2,0xad,0xb3,0xb2,0xab,0xad,0xae,0xb2,0xb0,0xab,0xb0,0xac,0xb2,0xb6, + 0xb2,0xb4,0xb4,0xb4,0xb7,0xd0,0xfb,0xf8,0xf9,0xfc,0xff,0xc7,0xbf,0xba,0xab,0x86, + 0x5f,0x56,0x54,0x5e,0x67,0x5d,0x52,0x56,0x5a,0x43,0x15,0x13,0x1f,0x1e,0x12,0x1d, + 0x45,0xbb,0x39,0x4e,0xca,0xcf,0xcf,0xd1,0xd2,0xd3,0xd4,0xd4,0xd4,0xd3,0xd4,0xd4, + 0xd4,0xd4,0xd4,0xd4,0xd2,0xd2,0xd6,0xd5,0xd5,0xd5,0xd5,0xd5,0xd2,0xd4,0xd5,0xe0, + 0x38,0x1c,0x3e,0x68,0x6a,0x68,0x62,0x5c,0x54,0x4e,0x48,0x44,0x41,0x43,0x47,0x4c, + 0x50,0x53,0x54,0x54,0x52,0x4d,0x4c,0x49,0x47,0x43,0x3f,0x3b,0x38,0x36,0x31,0x32, + 0x8c,0x89,0x88,0x88,0x89,0x87,0x89,0x8b,0x84,0x86,0x89,0x85,0x86,0x84,0x83,0x82, + 0x84,0x84,0x82,0x82,0x82,0x8a,0x85,0x83,0x64,0x7a,0xa8,0xc5,0xc6,0xc6,0xbe,0x90, + 0x87,0x8b,0x8a,0x89,0x91,0xa9,0xc8,0xc9,0xc9,0xc4,0xbf,0x87,0x6b,0x5e,0x5e,0x64, + 0x6b,0x70,0x7a,0x7f,0x81,0x89,0x8d,0x89,0x88,0x79,0x59,0x55,0x5c,0x54,0x4a,0x52, + 0x57,0xbf,0x38,0x50,0xcd,0xce,0xce,0xce,0xd1,0xd1,0xd3,0xd4,0xd5,0xd4,0xd4,0xd4, + 0xd4,0xd4,0xd3,0xd3,0xd3,0xd4,0xd4,0xd5,0xd5,0xd5,0xd5,0xd5,0xd6,0xd5,0xd4,0xe0, + 0x46,0x32,0x85,0x6b,0x6a,0x67,0x64,0x5e,0x55,0x50,0x49,0x48,0x48,0x49,0x4d,0x50, + 0x56,0x58,0x59,0x5b,0x58,0x55,0x51,0x4e,0x4b,0x47,0x42,0x3e,0x3b,0x38,0x36,0x33, + 0x8b,0x8a,0x8e,0x8b,0x8c,0x8d,0x8d,0x8d,0x8b,0x89,0x87,0x87,0x88,0x86,0x83,0x82, + 0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x80,0x76,0x7a,0x9c,0xde,0xe4,0xe5,0xe4,0xba,0x7e, + 0x7f,0x7f,0x7f,0x7f,0x7e,0x80,0x7e,0x80,0x81,0x7e,0x7c,0x76,0x6f,0x6d,0x73,0x84, + 0x93,0xa0,0xaf,0xc0,0xcd,0xd8,0xd9,0xd6,0xd1,0xcf,0xce,0xd6,0xde,0xe7,0xed,0xe8, + 0x9f,0xbe,0x39,0x50,0xcd,0xce,0xcc,0xd3,0xd4,0xd4,0xd4,0xd5,0xd5,0xd4,0xd4,0xd4, + 0xd4,0xd4,0xd5,0xd5,0xd4,0xd4,0xd4,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd4,0xd4,0xdf, + 0x44,0x32,0x7e,0x68,0x68,0x65,0x60,0x5d,0x57,0x53,0x50,0x51,0x51,0x52,0x54,0x58, + 0x5d,0x5f,0x5e,0x5e,0x5c,0x5b,0x56,0x52,0x52,0x4d,0x4a,0x45,0x43,0x42,0x3e,0x3b, + 0x88,0x89,0x8b,0x8f,0x8d,0x8b,0x8b,0x8b,0x8c,0x88,0x87,0x87,0x87,0x84,0x82,0x81, + 0x81,0x80,0x7e,0x7e,0x7f,0x81,0x75,0x71,0x96,0xd3,0xe5,0xe5,0xe6,0xd7,0x8e,0x7e, + 0x7f,0x7e,0x7e,0x7f,0x7f,0x7e,0x7f,0x7e,0x7d,0x7b,0x79,0x72,0x6c,0x6d,0x79,0x89, + 0x9d,0xaa,0xba,0xcb,0xd6,0xdb,0xd9,0xcd,0xc0,0xb8,0xbb,0xc5,0xd7,0xe3,0xed,0xf0, + 0xa3,0xbd,0x39,0x53,0xcc,0xc7,0xcd,0xd0,0xd1,0xd4,0xd4,0xd5,0xd5,0xd3,0xd2,0xd4, + 0xd5,0xd4,0xd5,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd5,0xd3,0xd4,0xd4,0xdf, + 0x46,0x32,0x83,0x66,0x64,0x60,0x5d,0x5c,0x5a,0x58,0x56,0x57,0x58,0x5a,0x5e,0x63, + 0x67,0x67,0x65,0x64,0x62,0x5f,0x5b,0x59,0x56,0x54,0x4f,0x4d,0x4b,0x49,0x45,0x44, + 0x88,0x8a,0x8f,0x8e,0x8d,0x8b,0x8b,0x8a,0x8c,0x87,0x86,0x86,0x86,0x84,0x81,0x7f, + 0x80,0x7f,0x82,0x80,0x7b,0x79,0x69,0x90,0xd0,0xe2,0xe4,0xe3,0xe1,0xa9,0x7f,0x81, + 0x82,0x82,0x82,0x81,0x7e,0x7f,0x7d,0x7d,0x7d,0x7b,0x76,0x6f,0x6a,0x6f,0x7f,0x97, + 0xa4,0xb1,0xc3,0xd1,0xd7,0xdb,0xd5,0xc0,0xa4,0x91,0x96,0xb1,0xca,0xdd,0xea,0xef, + 0xa7,0xba,0x3c,0x4d,0xca,0xce,0xd3,0xd0,0xd2,0xd4,0xd4,0xd5,0xd5,0xd3,0xd3,0xd4, + 0xd6,0xd4,0xd5,0xd5,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd5,0xd4,0xd4,0xdf, + 0x47,0x31,0x81,0x64,0x61,0x61,0x5e,0x5e,0x5c,0x5d,0x5b,0x5d,0x5c,0x5f,0x65,0x6a, + 0x6a,0x6b,0x6b,0x6b,0x69,0x62,0x61,0x61,0x60,0x59,0x54,0x54,0x53,0x50,0x4f,0x50, + 0x8c,0x8d,0x8f,0x8e,0x8d,0x8c,0x8b,0x89,0x88,0x86,0x86,0x85,0x85,0x83,0x81,0x81, + 0x81,0x81,0x7d,0x6c,0x78,0x61,0x80,0xca,0xe5,0xe5,0xe4,0xe5,0xc2,0x83,0x80,0x81, + 0x82,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7b,0x7a,0x79,0x74,0x6c,0x6a,0x73,0x89,0x9e, + 0xa7,0xb3,0xc4,0xce,0xd5,0xd6,0xcf,0xb7,0x96,0x74,0x72,0x95,0xb6,0xd4,0xe4,0xeb, + 0xa8,0xb9,0x3b,0x53,0xd1,0xcf,0xd0,0xd1,0xd2,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3,0xd4, + 0xd4,0xd4,0xd4,0xd3,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xdf, + 0x48,0x32,0x7f,0x60,0x62,0x62,0x60,0x5f,0x5e,0x5e,0x5f,0x5f,0x61,0x65,0x68,0x6b, + 0x6b,0x6c,0x6d,0x6a,0x67,0x66,0x65,0x64,0x60,0x58,0x56,0x56,0x58,0x58,0x58,0x58, + 0x8d,0x8e,0x8f,0x8f,0x8d,0x8d,0x8a,0x88,0x87,0x85,0x85,0x84,0x83,0x82,0x81,0x81, + 0x81,0x7c,0x5c,0x60,0x5e,0x7b,0xbd,0xeb,0xea,0xec,0xe8,0xdb,0x9b,0x78,0x7b,0x7f, + 0x82,0x82,0x81,0x80,0x7e,0x7d,0x7c,0x79,0x78,0x76,0x71,0x68,0x6a,0x7c,0x92,0xa0, + 0xac,0xb8,0xc6,0xcb,0xcf,0xd0,0xc5,0xad,0x90,0x75,0x6a,0x89,0xaf,0xca,0xde,0xe6, + 0xa4,0xbc,0x39,0x56,0xd1,0xd0,0xd1,0xd2,0xd1,0xd2,0xd3,0xd3,0xd3,0xd3,0xd3,0xd3, + 0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xd4,0xe0, + 0x48,0x31,0x86,0x63,0x62,0x61,0x60,0x61,0x61,0x64,0x63,0x63,0x66,0x6a,0x6e,0x6e, + 0x6f,0x6e,0x6c,0x67,0x68,0x69,0x66,0x62,0x5f,0x5b,0x5a,0x5b,0x5c,0x5e,0x5e,0x5e, + 0x8b,0x8f,0x8e,0x8d,0x8b,0x8a,0x87,0x87,0x85,0x84,0x84,0x84,0x82,0x82,0x7f,0x7f, + 0x85,0x63,0x52,0x55,0x71,0xc3,0xe5,0xee,0xef,0xf5,0xee,0xce,0xa4,0x7c,0x72,0x79, + 0x7c,0x81,0x80,0x7f,0x7d,0x7b,0x79,0x78,0x76,0x73,0x6f,0x68,0x6d,0x81,0x92,0xa6, + 0xb4,0xbb,0xc3,0xc7,0xcd,0xcc,0xbc,0xa5,0x96,0x8e,0x8e,0x9c,0xb1,0xc8,0xd8,0xe1, + 0xa0,0xbd,0x3b,0x55,0xd0,0xcd,0xd2,0xd1,0xd2,0xd2,0xd3,0xd3,0xd4,0xe1,0xdf,0xde, + 0xda,0xdc,0xd7,0xd4,0xd4,0xd6,0xd2,0xd3,0xd1,0xd4,0xd0,0xd5,0xd4,0xd4,0xd4,0xdf, + 0x48,0x31,0x7f,0x69,0x63,0x63,0x62,0x64,0x65,0x66,0x66,0x65,0x6a,0x6a,0x6b,0x6c, + 0x6e,0x6e,0x6a,0x66,0x66,0x67,0x65,0x61,0x5e,0x5c,0x5b,0x59,0x59,0x5d,0x5e,0x5c, + 0x8c,0x8e,0x8d,0x8b,0x8a,0x89,0x87,0x86,0x84,0x84,0x84,0x84,0x83,0x83,0x73,0x77, + 0x71,0x4d,0x51,0x5c,0xc1,0xe4,0xee,0xef,0xf7,0xf7,0xe3,0xc2,0xb1,0x97,0x75,0x6f, + 0x75,0x79,0x7e,0x7c,0x7b,0x78,0x76,0x75,0x73,0x70,0x6e,0x68,0x6e,0x80,0x95,0xad, + 0xbb,0xbd,0xc1,0xc5,0xcb,0xc7,0xb9,0xb1,0xac,0xa9,0xad,0xb8,0xbd,0xc3,0xcd,0xd5, + 0x9e,0xb9,0x3b,0x59,0xd0,0xd0,0xd1,0xd1,0xd2,0xd2,0xd2,0xd3,0xcf,0x3e,0x3b,0x46, + 0x4e,0x5c,0x65,0x71,0x7d,0x88,0x92,0x99,0xa1,0xa3,0xca,0xd3,0xd4,0xd3,0xd2,0xdf, + 0x48,0x33,0x81,0x6b,0x65,0x67,0x67,0x68,0x6a,0x69,0x68,0x68,0x65,0x69,0x6b,0x6a, + 0x6c,0x6d,0x69,0x64,0x64,0x64,0x64,0x5f,0x5f,0x5b,0x59,0x55,0x53,0x57,0x59,0x57, + 0x8c,0x8d,0x8c,0x8b,0x89,0x88,0x85,0x85,0x84,0x86,0x86,0x83,0x82,0x79,0x6d,0x77, + 0x51,0x4b,0x4f,0xba,0xe2,0xee,0xee,0xf6,0xfa,0xec,0xbe,0xb8,0xad,0x9d,0x87,0x6f, + 0x6b,0x70,0x76,0x79,0x77,0x74,0x74,0x72,0x71,0x6e,0x6a,0x66,0x6e,0x81,0x9c,0xb5, + 0xbe,0xbf,0xc3,0xc7,0xcb,0xc7,0xc2,0xbf,0xbd,0xbc,0xc3,0xc9,0xc8,0xc2,0xc6,0xd0, + 0x9b,0xb5,0x3a,0x53,0xcc,0xce,0xd2,0xd1,0xd1,0xcf,0xd0,0xd2,0xce,0x2c,0x13,0x10, + 0x0b,0x0b,0x0f,0x18,0x15,0x10,0x15,0x17,0x15,0x0e,0x9a,0xd6,0xd1,0xd3,0xd4,0xdf, + 0x48,0x33,0x7d,0x6a,0x66,0x65,0x68,0x69,0x6a,0x6a,0x68,0x69,0x65,0x68,0x69,0x68, + 0x69,0x6a,0x68,0x65,0x65,0x65,0x62,0x62,0x5f,0x5b,0x55,0x52,0x4d,0x50,0x52,0x52, + 0x8c,0x8d,0x8c,0x89,0x89,0x87,0x86,0x85,0x86,0x88,0x86,0x84,0x79,0x5d,0x70,0x5f, + 0x4c,0x56,0xad,0xea,0xef,0xef,0xf3,0xfc,0xf0,0x8e,0x83,0xa4,0xa7,0x95,0x83,0x70, + 0x5d,0x64,0x6a,0x72,0x72,0x74,0x72,0x71,0x70,0x6d,0x69,0x65,0x70,0x8a,0xa8,0xb9, + 0xc1,0xc6,0xca,0xce,0xcd,0xcb,0xca,0xca,0xcb,0xcb,0xcc,0xce,0xd0,0xce,0xc9,0xcd, + 0x97,0xb5,0x39,0x54,0xcc,0xd0,0xd1,0xd1,0xd2,0xd3,0xd3,0xd3,0xd3,0xdd,0xdb,0xd5, + 0xcf,0xc8,0x98,0x13,0x46,0xa8,0x99,0x8e,0x86,0x79,0xb8,0xd5,0xd4,0xd3,0xd3,0xde, + 0x47,0x31,0x7c,0x67,0x63,0x62,0x66,0x68,0x6c,0x6e,0x6d,0x6b,0x67,0x69,0x69,0x6b, + 0x6b,0x6b,0x68,0x66,0x66,0x66,0x66,0x64,0x60,0x58,0x53,0x50,0x4b,0x47,0x48,0x4d, + 0x8b,0x8c,0x8b,0x88,0x88,0x87,0x84,0x86,0x87,0x86,0x84,0x81,0x5f,0x5c,0x6a,0x4d, + 0x55,0x9b,0xe4,0xed,0xee,0xf0,0xf7,0xf9,0x9a,0x23,0x3e,0x72,0x8f,0x88,0x7a,0x61, + 0x4e,0x56,0x5d,0x67,0x70,0x72,0x71,0x6f,0x6e,0x6a,0x68,0x64,0x75,0x94,0xad,0xbf, + 0xc6,0xcc,0xd0,0xd0,0xd1,0xd2,0xd3,0xd3,0xd2,0xcc,0xce,0xd2,0xd3,0xd1,0xce,0xcc, + 0x99,0xb1,0x3d,0x58,0xce,0xcc,0xcc,0xd2,0xd2,0xd1,0xd2,0xd2,0xd2,0xd2,0xd3,0xd1, + 0xd3,0xd4,0xa7,0x0f,0x52,0xdc,0xd6,0xd4,0xd2,0xd3,0xd3,0xd4,0xd4,0xd2,0xd3,0xdd, + 0x49,0x32,0x7f,0x64,0x5d,0x5e,0x5f,0x66,0x6d,0x6d,0x6a,0x68,0x67,0x68,0x6a,0x6d, + 0x6c,0x6d,0x68,0x67,0x68,0x67,0x66,0x65,0x61,0x56,0x51,0x4d,0x48,0x43,0x48,0x4c, + 0x8d,0x8c,0x8b,0x89,0x88,0x86,0x84,0x87,0x8a,0x85,0x84,0x69,0x4e,0x69,0x52,0x57, + 0x8f,0xe5,0xed,0xed,0xf0,0xf8,0xf8,0xae,0x1b,0x20,0x2d,0x3f,0x64,0x6e,0x66,0x53, + 0x3a,0x3f,0x4c,0x5d,0x6a,0x6f,0x6f,0x6e,0x6b,0x6c,0x6a,0x68,0x82,0xa1,0xb7,0xc8, + 0xce,0xd2,0xd4,0xd4,0xd5,0xd8,0xd5,0xd1,0xcd,0xd1,0xd0,0xd3,0xd6,0xd8,0xd9,0xd5, + 0x95,0xb3,0x3c,0x5d,0xd3,0xd0,0xd1,0xd2,0xd3,0xd2,0xd1,0xd2,0xd2,0xd2,0xd1,0xd3, + 0xd2,0xd2,0xa1,0x11,0x51,0xde,0xd4,0xd4,0xd3,0xd4,0xd2,0xd4,0xd3,0xd4,0xd3,0xde, + 0x4a,0x34,0x80,0x64,0x5d,0x5d,0x5a,0x61,0x67,0x67,0x67,0x68,0x68,0x68,0x69,0x6a, + 0x6b,0x6e,0x68,0x67,0x69,0x67,0x66,0x62,0x5a,0x4f,0x4c,0x4c,0x49,0x46,0x4a,0x4f, + 0x8c,0x8a,0x89,0x89,0x86,0x83,0x82,0x8c,0x8a,0x83,0x79,0x49,0x53,0x5e,0x4b,0x75, + 0xcf,0xe9,0xf1,0xed,0xf8,0xfa,0xc9,0x2f,0x1c,0x24,0x30,0x38,0x42,0x4f,0x4f,0x40, + 0x2e,0x27,0x3a,0x51,0x62,0x6e,0x6f,0x6d,0x6b,0x6e,0x6c,0x6e,0x8a,0xa9,0xbf,0xcf, + 0xd5,0xd6,0xd6,0xd6,0xd6,0xd3,0xd0,0xce,0xce,0xd1,0xd2,0xd6,0xd9,0xdd,0xe1,0xe0, + 0x91,0xb3,0x3b,0x5e,0xd0,0xd1,0xd3,0xd1,0xd1,0xd1,0xd1,0xd2,0xd0,0xbc,0xc2,0xc8, + 0xc9,0xcb,0xa2,0x0d,0x4e,0xd6,0xcb,0xcf,0xce,0xcd,0xd5,0xd3,0xd3,0xd3,0xd3,0xde, + 0x49,0x33,0x7f,0x64,0x5d,0x5c,0x59,0x5c,0x5e,0x5e,0x61,0x64,0x66,0x68,0x64,0x63, + 0x62,0x63,0x60,0x5f,0x63,0x61,0x5f,0x5a,0x54,0x4b,0x4a,0x47,0x44,0x46,0x4c,0x4f, + 0x8b,0x87,0x87,0x87,0x85,0x83,0x84,0x8e,0x87,0x82,0x5c,0x41,0x5d,0x46,0x64,0xb4, + 0xe0,0xec,0xf2,0xf7,0xfa,0xcc,0x38,0x1d,0x1f,0x23,0x28,0x31,0x3d,0x50,0x53,0x45, + 0x33,0x1e,0x2d,0x46,0x5a,0x6a,0x6d,0x6c,0x6c,0x6e,0x6f,0x71,0x90,0xad,0xc4,0xd3, + 0xd8,0xd8,0xd5,0xd4,0xd0,0xce,0xcd,0xce,0xcf,0xd2,0xd5,0xd9,0xdd,0xe4,0xe6,0xe3, + 0x96,0xaf,0x3a,0x5d,0xd2,0xcf,0xd0,0xd2,0xd1,0xd1,0xd0,0xd1,0xcc,0x21,0x0f,0x14, + 0x18,0x1d,0x1e,0x19,0x1d,0x2e,0x33,0x36,0x3a,0x36,0xa9,0xd4,0xd4,0xd2,0xd1,0xde, + 0x49,0x34,0x7c,0x69,0x5f,0x5b,0x55,0x57,0x58,0x57,0x5b,0x60,0x62,0x60,0x5e,0x5c, + 0x59,0x55,0x50,0x52,0x58,0x56,0x53,0x51,0x4a,0x43,0x41,0x3e,0x3d,0x42,0x49,0x4a, + 0x89,0x88,0x89,0x85,0x84,0x83,0x84,0x86,0x7e,0x70,0x41,0x51,0x52,0x5a,0x93,0xd0, + 0xe1,0xee,0xf4,0xf9,0xdc,0x4c,0x24,0x23,0x22,0x23,0x23,0x2b,0x49,0x6c,0x74,0x68, + 0x4f,0x2e,0x2a,0x3f,0x53,0x64,0x6a,0x6b,0x6c,0x6f,0x72,0x73,0x91,0xae,0xc5,0xd5, + 0xd8,0xd3,0xd1,0xcd,0xcf,0xcf,0xcf,0xd0,0xd2,0xd6,0xd9,0xdd,0xe3,0xe4,0xe5,0xe2, + 0x99,0xab,0x3c,0x5f,0xd1,0xce,0xd0,0xd1,0xd1,0xd0,0xd1,0xd1,0xd0,0x7b,0x66,0x5a, + 0x4f,0x45,0x3b,0x37,0x31,0x30,0x2b,0x2a,0x26,0x1e,0x9e,0xd5,0xd3,0xd3,0xd3,0xdd, + 0x4a,0x34,0x7a,0x66,0x5d,0x57,0x50,0x52,0x52,0x52,0x5a,0x5b,0x5d,0x5b,0x59,0x52, + 0x4c,0x46,0x44,0x44,0x4a,0x49,0x48,0x46,0x40,0x38,0x38,0x35,0x35,0x3b,0x42,0x42, + 0x89,0x89,0x87,0x84,0x83,0x83,0x87,0x78,0x77,0x52,0x3d,0x5b,0x5a,0x89,0xc8,0xde, + 0xe6,0xf3,0xfb,0xe3,0x62,0x35,0x33,0x31,0x2b,0x2d,0x2b,0x35,0x55,0x80,0x99,0x97, + 0x7e,0x57,0x33,0x3b,0x4e,0x5e,0x66,0x6c,0x6d,0x71,0x75,0x76,0x8c,0xae,0xc2,0xcf, + 0xd0,0xcf,0xcd,0xcf,0xd0,0xd1,0xd2,0xd4,0xd7,0xdb,0xdf,0xe2,0xe2,0xe4,0xe6,0xe3, + 0x99,0xa8,0x3a,0x61,0xd1,0xce,0xd0,0xd0,0xd1,0xd2,0xd1,0xd1,0xd1,0xd7,0xd3,0xe5, + 0xdc,0xd5,0xd5,0xd3,0xd0,0xce,0xc9,0xca,0xc8,0xc6,0xcd,0xd2,0xd2,0xd2,0xd2,0xdd, + 0x4a,0x33,0x7e,0x62,0x58,0x50,0x4b,0x4a,0x4c,0x50,0x59,0x5f,0x5c,0x5a,0x55,0x4e, + 0x42,0x3b,0x38,0x3b,0x3c,0x3a,0x39,0x3a,0x36,0x30,0x2f,0x2d,0x2c,0x2f,0x35,0x3a, + 0x89,0x87,0x83,0x82,0x82,0x85,0x81,0x67,0x6b,0x36,0x4c,0x5f,0x78,0xc4,0xdd,0xe2, + 0xf1,0xfa,0xea,0x9b,0x6c,0x60,0x50,0x3a,0x33,0x34,0x32,0x41,0x68,0x8f,0xad,0xb5, + 0xac,0x8e,0x69,0x4f,0x52,0x59,0x64,0x6b,0x6f,0x73,0x77,0x78,0x89,0xa5,0xb8,0xc3, + 0xce,0xcf,0xcf,0xd0,0xd3,0xd5,0xd7,0xd9,0xdd,0xde,0xe0,0xe1,0xe1,0xe1,0xe3,0xe0, + 0x99,0xa9,0x3b,0x63,0xd2,0xcf,0xd0,0xd1,0xd1,0xd0,0xd1,0xd1,0xd2,0xc9,0x77,0x3d, + 0x53,0x9f,0xd2,0xc8,0xaa,0xd2,0xd3,0xd3,0xd3,0xd3,0xd4,0xd1,0xd2,0xd1,0xd2,0xde, + 0x4a,0x31,0x7e,0x57,0x4e,0x4a,0x48,0x46,0x4d,0x50,0x58,0x5e,0x5b,0x5a,0x55,0x4e, + 0x40,0x36,0x30,0x2d,0x2f,0x2e,0x31,0x33,0x2f,0x2c,0x29,0x26,0x23,0x21,0x27,0x2f, + 0x87,0x83,0x7c,0x7e,0x85,0x83,0x6d,0x63,0x50,0x33,0x60,0x68,0xb2,0xde,0xdf,0xe9, + 0xf9,0xee,0xc2,0xb8,0xb8,0xae,0x8d,0x5d,0x3c,0x3c,0x49,0x5e,0x83,0xa4,0xba,0xc3, + 0xc5,0xbc,0xa9,0x85,0x6e,0x5f,0x63,0x6c,0x71,0x76,0x7a,0x7a,0x84,0x99,0xae,0xbe, + 0xcb,0xcf,0xd1,0xd3,0xd6,0xda,0xdf,0xdf,0xde,0xdf,0xe0,0xe0,0xe0,0xe0,0xe0,0xdf, + 0x96,0xa8,0x3d,0x63,0xd4,0xd0,0xd0,0xd0,0xd0,0xcc,0xd0,0xd0,0xd1,0x40,0x17,0x0e, + 0x12,0x18,0x8b,0xc3,0x1c,0x3d,0xce,0xd2,0xd3,0xd2,0xd2,0xd1,0xd1,0xd2,0xd2,0xdd, + 0x4a,0x32,0x7e,0x4c,0x41,0x45,0x45,0x47,0x4d,0x4e,0x55,0x59,0x5c,0x5c,0x57,0x4f, + 0x3f,0x35,0x2e,0x26,0x28,0x28,0x2d,0x31,0x31,0x2b,0x26,0x20,0x1a,0x1d,0x23,0x2a, + 0x86,0x7f,0x71,0x7f,0x86,0x7f,0x5a,0x59,0x35,0x4d,0x6e,0xaa,0xd8,0xde,0xe5,0xf4, + 0xec,0xd1,0xd0,0xdb,0xdf,0xd0,0xad,0x7f,0x51,0x4e,0x69,0x8c,0xa5,0xb1,0xbd,0xc5, + 0xcd,0xd2,0xcc,0xb3,0x8f,0x70,0x68,0x6e,0x74,0x79,0x7d,0x7c,0x7f,0x8f,0xa3,0xb8, + 0xc6,0xcf,0xd6,0xd8,0xdc,0xde,0xdf,0xde,0xdd,0xdd,0xdf,0xde,0xde,0xde,0xde,0xdd, + 0x92,0xa6,0x3d,0x66,0xd5,0xd0,0xd0,0xd0,0xd0,0xd1,0xd0,0xcf,0xc3,0x23,0x29,0xcb, + 0xa3,0x16,0x46,0xd2,0x52,0x0f,0x8f,0xd5,0xd2,0xd3,0xd2,0xd2,0xd0,0xd1,0xd2,0xdd, + 0x4b,0x34,0x7e,0x41,0x38,0x3e,0x40,0x46,0x4b,0x4e,0x53,0x56,0x5b,0x58,0x54,0x4e, + 0x43,0x38,0x2f,0x26,0x23,0x28,0x2c,0x2c,0x2d,0x29,0x26,0x1f,0x19,0x20,0x28,0x31, + 0x84,0x71,0x6f,0x84,0x86,0x6d,0x50,0x41,0x3f,0x5e,0xa7,0xda,0xdc,0xde,0xee,0xe6, + 0xc7,0xd3,0xe7,0xee,0xef,0xe4,0xc3,0x93,0x5f,0x5b,0x88,0xb3,0xc9,0xc0,0xbd,0xbd, + 0xc6,0xd5,0xd5,0xc3,0xa7,0x83,0x70,0x74,0x75,0x7b,0x7e,0x7f,0x7e,0x83,0x98,0xac, + 0xc0,0xcc,0xda,0xdb,0xdc,0xde,0xdd,0xdd,0xdc,0xdc,0xdc,0xdd,0xdc,0xdd,0xde,0xdd, + 0x94,0xa2,0x3f,0x69,0xd3,0xcf,0xd0,0xd1,0xd0,0xd0,0xd0,0xd0,0xc7,0x27,0x37,0xce, + 0xc2,0x11,0x37,0xdf,0xd1,0x0b,0x38,0xe0,0xd0,0xd2,0xd2,0xd2,0xd2,0xd1,0xd2,0xdc, + 0x4c,0x34,0x7a,0x3f,0x34,0x3c,0x41,0x46,0x49,0x49,0x4b,0x51,0x55,0x54,0x53,0x4a, + 0x43,0x39,0x2b,0x22,0x24,0x26,0x29,0x29,0x2a,0x28,0x26,0x21,0x21,0x28,0x2f,0x37, + 0x82,0x65,0x79,0x87,0x7d,0x57,0x4b,0x3c,0x3d,0xa1,0xdd,0xdd,0xde,0xe3,0xd8,0xb4, + 0xbc,0xdc,0xef,0xf5,0xf4,0xe9,0xc7,0x95,0x66,0x6b,0x9b,0xc6,0xdb,0xd2,0xba,0xa9, + 0xa8,0xc3,0xc6,0xbc,0xa9,0x87,0x73,0x77,0x7b,0x80,0x82,0x80,0x82,0x80,0x8c,0xa3, + 0xb7,0xc8,0xd4,0xdb,0xdc,0xdc,0xdb,0xdc,0xda,0xda,0xdb,0xdc,0xdb,0xdc,0xdd,0xdc, + 0x91,0xa2,0x3f,0x6b,0xd1,0xcf,0xcf,0xd0,0xd0,0xd0,0xd0,0xd0,0xd1,0x75,0x13,0x7f, + 0xd0,0x29,0x22,0xd7,0xb6,0x0c,0x47,0xde,0xd2,0xd2,0xd2,0xd2,0xd1,0xd1,0xd2,0xdd, + 0x4c,0x35,0x7d,0x41,0x3a,0x40,0x45,0x49,0x48,0x49,0x48,0x4a,0x4e,0x4e,0x4c,0x48, + 0x40,0x35,0x27,0x21,0x24,0x25,0x27,0x29,0x29,0x2a,0x26,0x25,0x29,0x2f,0x34,0x39, + 0x70,0x64,0x80,0x86,0x73,0x44,0x34,0x2a,0x7a,0xd7,0xe0,0xdd,0xdc,0xce,0xb1,0xa9, + 0xc5,0xe3,0xf0,0xf6,0xf1,0xe0,0xba,0x84,0x5c,0x7b,0xaf,0xd4,0xe4,0xdd,0xbe,0x8b, + 0x6b,0x8f,0xa2,0x9f,0x92,0x7a,0x72,0x79,0x82,0x84,0x84,0x83,0x84,0x83,0x83,0x93, + 0xa8,0xbd,0xca,0xd5,0xd9,0xd9,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xdb,0xda, + 0x91,0xa0,0x3d,0x6d,0xd2,0xce,0xcf,0xd0,0xd0,0xd0,0xd0,0xcf,0xca,0x23,0x19,0x14, + 0x0b,0x16,0x17,0x0f,0x14,0x17,0xaf,0xd1,0xd2,0xd2,0xd2,0xd2,0xd0,0xd2,0xd0,0xdd, + 0x4d,0x35,0x7b,0x46,0x41,0x43,0x49,0x4e,0x4f,0x4f,0x4a,0x49,0x4b,0x49,0x48,0x44, + 0x3c,0x34,0x29,0x24,0x25,0x27,0x2a,0x2d,0x2d,0x2c,0x2c,0x2f,0x31,0x37,0x3b,0x3f, + 0x63,0x6a,0x7f,0x81,0x62,0x22,0x25,0x6e,0xd8,0xe1,0xe1,0xd9,0xc5,0xb5,0xab,0xa8, + 0xc3,0xe1,0xf0,0xf4,0xeb,0xd4,0xa7,0x6a,0x63,0x92,0xc0,0xde,0xe8,0xdc,0xbf,0x7a, + 0x30,0x3b,0x60,0x6d,0x6a,0x66,0x72,0x7b,0x81,0x86,0x86,0x85,0x85,0x84,0x84,0x86, + 0x94,0xaa,0xbc,0xc9,0xd4,0xd7,0xd8,0xd8,0xd9,0xd9,0xd9,0xd9,0xd9,0xd9,0xdb,0xd7, + 0x8c,0x9b,0x3c,0x6e,0xd2,0xce,0xcf,0xcf,0xd0,0xd0,0xd0,0xcf,0xcd,0x7b,0x61,0x55, + 0x48,0x3b,0x2f,0x26,0x46,0xa5,0xd2,0xd1,0xd1,0xd2,0xd1,0xd1,0xd1,0xd1,0xd1,0xdb, + 0x4f,0x35,0x75,0x47,0x40,0x45,0x4d,0x52,0x52,0x51,0x4b,0x4a,0x46,0x48,0x46,0x3f, + 0x38,0x33,0x2f,0x28,0x2a,0x2b,0x2c,0x31,0x30,0x32,0x32,0x38,0x3d,0x42,0x45,0x49, + 0x5e,0x75,0x7e,0x7a,0x42,0x2a,0x4c,0xd5,0xee,0xe5,0xd4,0xbf,0xb4,0xb1,0xaa,0xa7, + 0xba,0xd3,0xe1,0xe5,0xdb,0xbe,0x8d,0x57,0x79,0xa5,0xcb,0xe0,0xe6,0xdb,0xb9,0x70, + 0x23,0x1a,0x29,0x40,0x52,0x64,0x72,0x7b,0x81,0x87,0x87,0x86,0x86,0x85,0x85,0x85, + 0x87,0x9a,0xaf,0xbe,0xca,0xd3,0xd6,0xd7,0xd8,0xd8,0xd8,0xd7,0xd7,0xd6,0xd6,0xcb, + 0x88,0x99,0x3b,0x6f,0xd1,0xce,0xcf,0xcf,0xcf,0xcf,0xcf,0xd0,0xd1,0xd2,0xd6,0xdd, + 0xe0,0xe1,0xe4,0xe0,0xda,0xce,0xd2,0xd1,0xd1,0xd1,0xd1,0xd2,0xd1,0xd1,0xd1,0xda, + 0x4e,0x35,0x73,0x4c,0x43,0x4a,0x4f,0x54,0x55,0x50,0x4c,0x48,0x45,0x46,0x44,0x3f, + 0x3a,0x36,0x34,0x30,0x30,0x32,0x34,0x37,0x38,0x3c,0x3e,0x42,0x48,0x4c,0x4c,0x4a, + 0x61,0x7d,0x80,0x60,0x39,0x3f,0xca,0xed,0xec,0xd8,0xb7,0xab,0xb2,0xb2,0xaa,0xa2, + 0xac,0xbd,0xca,0xcc,0xc2,0xa2,0x6a,0x5a,0x8a,0xb2,0xd2,0xe5,0xe8,0xdd,0xb5,0x71, + 0x1e,0x1e,0x2a,0x3c,0x51,0x64,0x72,0x7c,0x82,0x86,0x87,0x87,0x86,0x85,0x86,0x86, + 0x87,0x8e,0xa0,0xb3,0xc2,0xcf,0xd5,0xd6,0xd6,0xd5,0xd7,0xd5,0xd3,0xcd,0xc5,0xb8, + 0x81,0x95,0x3a,0x71,0xd1,0xcd,0xcd,0xd0,0xd0,0xcf,0xce,0xd0,0xd1,0xcd,0xcc,0x68, + 0x4a,0x54,0x61,0x79,0xca,0xd4,0xd4,0xd2,0xd0,0xd1,0xd1,0xd1,0xd1,0xd1,0xd0,0xdc, + 0x4e,0x37,0x74,0x4f,0x45,0x49,0x4b,0x51,0x50,0x4c,0x47,0x46,0x45,0x45,0x46,0x41, + 0x3b,0x35,0x37,0x37,0x3c,0x3c,0x3f,0x40,0x43,0x46,0x4a,0x4b,0x4f,0x50,0x4d,0x4a, + 0x6d,0x80,0x5e,0x35,0x31,0xa6,0xee,0xef,0xdb,0xb8,0xaa,0xab,0xb1,0xb2,0xac,0xa3, + 0x8e,0x93,0xa5,0xa8,0x9c,0x80,0x5c,0x63,0x91,0xb9,0xd9,0xe9,0xe7,0xd9,0xb1,0x6d, + 0x22,0x24,0x33,0x44,0x56,0x67,0x75,0x7e,0x83,0x86,0x87,0x87,0x86,0x85,0x86,0x87, + 0x88,0x8a,0x92,0xa5,0xb6,0xc6,0xcf,0xd4,0xd5,0xd3,0xd2,0xce,0xc4,0xb8,0xaa,0x9a, + 0x7a,0x94,0x3b,0x73,0xd2,0xcd,0xce,0xd1,0xd0,0xcf,0xcf,0xcf,0xd1,0xa4,0x15,0x0c, + 0x16,0x12,0x0e,0x0c,0x0a,0x79,0xce,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd0,0xdc, + 0x4e,0x36,0x74,0x4b,0x3e,0x41,0x43,0x46,0x46,0x46,0x43,0x47,0x49,0x46,0x44,0x42, + 0x3d,0x38,0x39,0x3e,0x47,0x47,0x45,0x46,0x4a,0x4d,0x4f,0x52,0x55,0x54,0x50,0x4a, + 0x77,0x93,0x5f,0x1e,0x99,0xe7,0xe1,0xc6,0xb2,0xb0,0xad,0xaa,0xaf,0xb0,0xae,0xa4, + 0x89,0x7a,0x7b,0x7a,0x77,0x6a,0x5e,0x67,0x8e,0xb8,0xd8,0xe8,0xe5,0xd5,0xab,0x67, + 0x2c,0x2f,0x3c,0x4b,0x5d,0x6d,0x7a,0x80,0x83,0x85,0x86,0x86,0x86,0x85,0x86,0x86, + 0x88,0x8a,0x8c,0x99,0xac,0xbe,0xc9,0xd3,0xd3,0xd0,0xc8,0xbc,0xae,0x9b,0x86,0x6c, + 0x73,0x90,0x3b,0x73,0xd0,0xcd,0xcf,0xcf,0xcf,0xce,0xcf,0xce,0xc8,0x31,0x15,0x8f, + 0xce,0xc6,0xc5,0xaa,0x29,0x14,0xa7,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd0,0xdc, + 0x4e,0x34,0x78,0x4a,0x3b,0x3b,0x3b,0x3c,0x3c,0x3d,0x40,0x48,0x48,0x45,0x43,0x45, + 0x41,0x3d,0x3e,0x47,0x4e,0x4a,0x4d,0x4e,0x4f,0x51,0x52,0x52,0x56,0x56,0x50,0x4a, + 0x85,0xa7,0x3b,0x67,0xdc,0xcd,0xad,0xa2,0xab,0xad,0xab,0xab,0xae,0xad,0xa8,0xa1, + 0x95,0x8c,0x81,0x6c,0x6a,0x65,0x59,0x60,0x8a,0xb6,0xd6,0xe3,0xdf,0xce,0xa0,0x64, + 0x35,0x3c,0x47,0x56,0x65,0x71,0x7a,0x81,0x85,0x84,0x85,0x85,0x85,0x83,0x84,0x85, + 0x87,0x8b,0x8d,0x90,0xa0,0xb4,0xc3,0xcb,0xca,0xc1,0xb1,0xa2,0x8a,0x6f,0x53,0x36, + 0x6b,0x91,0x3e,0x74,0xd0,0xce,0xce,0xcf,0xce,0xcc,0xce,0xcd,0xbe,0x21,0x38,0xce, + 0xcf,0xd0,0xd2,0xd0,0xaf,0x07,0x4e,0xdd,0xd1,0xd2,0xd1,0xd1,0xd0,0xd0,0xd0,0xd9, + 0x4e,0x35,0x78,0x4b,0x3b,0x3a,0x37,0x39,0x3b,0x3c,0x40,0x47,0x49,0x47,0x47,0x46, + 0x45,0x42,0x46,0x4c,0x4f,0x50,0x52,0x51,0x51,0x4f,0x52,0x51,0x55,0x53,0x4e,0x4b, + 0xbe,0xc3,0x85,0xc4,0xbe,0x95,0x95,0xa1,0xaa,0xa9,0xa9,0xa9,0xac,0xac,0xa6,0xa0, + 0x93,0x7e,0x77,0x69,0x61,0x5a,0x53,0x66,0x8e,0xab,0xc6,0xd2,0xd0,0xbc,0x90,0x57, + 0x44,0x48,0x4f,0x57,0x61,0x69,0x74,0x7d,0x83,0x84,0x86,0x84,0x84,0x85,0x83,0x84, + 0x86,0x8a,0x8d,0x8e,0x98,0xaa,0xb6,0xbb,0xb5,0xab,0x97,0x81,0x62,0x40,0x26,0x18, + 0x6a,0x8d,0x3d,0x78,0xcf,0xcd,0xcf,0xd0,0xcf,0xcf,0xd0,0xd1,0xc5,0x2b,0x1f,0xc6, + 0xcf,0xd0,0xcf,0xd1,0x9f,0x0b,0x4e,0xda,0xd2,0xd1,0xd1,0xd1,0xcf,0xce,0xd0,0xda, + 0x4f,0x35,0x75,0x4d,0x3a,0x38,0x37,0x37,0x3d,0x41,0x43,0x47,0x4b,0x4d,0x4a,0x47, + 0x45,0x43,0x47,0x4d,0x51,0x54,0x53,0x50,0x4e,0x4e,0x4f,0x4e,0x4f,0x4d,0x49,0x48, + 0xcf,0xd5,0xcd,0xaa,0x8b,0x8b,0x97,0xa0,0xa7,0xaa,0xaa,0xaa,0xab,0xaa,0xa5,0x9c, + 0x90,0x74,0x5f,0x57,0x45,0x42,0x54,0x73,0x93,0xa4,0xba,0xbf,0xb1,0x98,0x72,0x48, + 0x46,0x46,0x48,0x4a,0x50,0x5b,0x6d,0x77,0x81,0x83,0x85,0x85,0x85,0x8a,0x8a,0x89, + 0x87,0x89,0x8c,0x8e,0x93,0x9f,0xa7,0xaa,0xa6,0x99,0x81,0x68,0x47,0x2e,0x1d,0x14, + 0x6a,0x8c,0x3c,0x78,0xd0,0xce,0xce,0xce,0xce,0xce,0xcf,0xcf,0xd1,0x8e,0x0b,0x12, + 0xb7,0xd0,0xd0,0xa2,0x18,0x13,0xac,0xd1,0xd1,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xda, + 0x51,0x35,0x71,0x4e,0x3a,0x3b,0x3a,0x3b,0x3f,0x41,0x43,0x45,0x4a,0x4d,0x4d,0x49, + 0x45,0x45,0x48,0x4b,0x4d,0x52,0x54,0x4f,0x4b,0x4e,0x4e,0x4d,0x4c,0x4a,0x43,0x42, + 0xc8,0xb5,0x97,0x8d,0x8c,0x8f,0x99,0xa1,0xa8,0xab,0xab,0xac,0xad,0xaa,0xa2,0x97, + 0x87,0x79,0x60,0x43,0x39,0x44,0x61,0x85,0x9e,0xb8,0xb7,0xae,0x96,0x6d,0x50,0x3f, + 0x3f,0x3d,0x3c,0x39,0x3d,0x4e,0x63,0x72,0x7c,0x82,0x85,0x84,0x89,0x93,0x93,0x93, + 0x8e,0x89,0x8c,0x8f,0x92,0x9a,0x9f,0x9e,0x9c,0x8e,0x7a,0x6a,0x55,0x3e,0x2b,0x17, + 0x6d,0x88,0x3c,0x7a,0xd0,0xce,0xce,0xce,0xce,0xce,0xcf,0xcf,0xcf,0xd4,0xa4,0x21, + 0xaf,0xcd,0xd3,0x95,0x18,0x87,0xd3,0xd2,0xd2,0xd1,0xd0,0xd0,0xd0,0xd0,0xd0,0xda, + 0x53,0x36,0x74,0x50,0x3d,0x40,0x41,0x3c,0x3c,0x3c,0x3e,0x40,0x46,0x4a,0x4a,0x48, + 0x49,0x48,0x47,0x48,0x4a,0x50,0x53,0x4e,0x4f,0x4f,0x4f,0x4d,0x4f,0x4e,0x48,0x45, + 0x94,0x7d,0x80,0x8a,0x91,0x9b,0xa1,0xa3,0xa9,0xaa,0xaa,0xac,0xac,0xaa,0xa3,0x97, + 0x83,0x6e,0x55,0x3f,0x3d,0x48,0x68,0x8c,0xb2,0xca,0xbe,0xa3,0x7d,0x4f,0x37,0x32, + 0x31,0x2e,0x2b,0x24,0x28,0x40,0x5a,0x70,0x7f,0x83,0x84,0x85,0x8c,0x96,0x97,0x99, + 0x94,0x8b,0x8d,0x91,0x93,0x98,0x98,0x99,0x97,0x91,0x8b,0x82,0x71,0x5c,0x40,0x25, + 0x72,0x85,0x3b,0x7d,0xcf,0xcc,0xce,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xd2,0xd0, + 0xcc,0xce,0xcd,0xc8,0xc3,0xd4,0xcc,0xd0,0xd1,0xd0,0xd0,0xd0,0xd0,0xd0,0xd0,0xd9, + 0x51,0x37,0x74,0x4f,0x3e,0x40,0x3f,0x3a,0x39,0x38,0x3a,0x3c,0x41,0x46,0x4a,0x4a, + 0x47,0x46,0x44,0x45,0x4a,0x51,0x53,0x4f,0x51,0x4f,0x4f,0x4f,0x52,0x50,0x4d,0x47, + 0x6c,0x75,0x7e,0x8b,0x9d,0xab,0xaa,0xa9,0xa7,0xa6,0xa7,0xa9,0xac,0xa8,0xa4,0x9a, + 0x8a,0x74,0x5d,0x4a,0x43,0x4b,0x6c,0x9c,0xc7,0xd6,0xc9,0xa6,0x79,0x3a,0x21,0x22, + 0x1f,0x1d,0x1b,0x1a,0x22,0x40,0x5d,0x74,0x82,0x85,0x83,0x84,0x8c,0x99,0x9b,0x9b, + 0x95,0x8d,0x8d,0x8f,0x93,0x97,0x9b,0x9c,0x9f,0x9f,0xa3,0x9c,0x8b,0x74,0x57,0x35, + 0x73,0x82,0x3d,0x7b,0xcd,0xcc,0xcd,0xcf,0xce,0xce,0xcf,0xce,0xc9,0x91,0x92,0x9c, + 0xa7,0xac,0xb2,0xb1,0xbb,0xbb,0xbe,0xc1,0xc2,0xc3,0xcf,0xd1,0xd1,0xd0,0xcf,0xda, + 0x54,0x37,0x73,0x52,0x3c,0x3d,0x3b,0x38,0x37,0x35,0x35,0x36,0x3d,0x47,0x4a,0x4a, + 0x48,0x44,0x41,0x44,0x48,0x4f,0x54,0x54,0x51,0x51,0x52,0x53,0x52,0x56,0x53,0x4c, + 0x72,0x75,0x7e,0x87,0xa2,0xb4,0xb4,0xb0,0xa5,0xa2,0xa4,0xa4,0xa6,0xa5,0xa1,0x98, + 0x8c,0x7b,0x67,0x52,0x4b,0x53,0x76,0xaf,0xcd,0xd5,0xc2,0xa4,0x68,0x23,0x1a,0x1b, + 0x1a,0x1a,0x1a,0x19,0x1f,0x3d,0x5c,0x74,0x83,0x84,0x83,0x82,0x88,0x93,0x97,0x97, + 0x92,0x8d,0x8b,0x8f,0x93,0x98,0x9c,0xa0,0xa5,0xa9,0xae,0xad,0x9f,0x8b,0x6c,0x48, + 0x75,0x7f,0x3b,0x82,0xcd,0xcc,0xcc,0xcc,0xcc,0xcd,0xcd,0xcd,0xb8,0x1b,0x12,0x16, + 0x18,0x17,0x16,0x1b,0x1a,0x1e,0x20,0x21,0x21,0x1b,0x9e,0xd2,0xd0,0xcf,0xcf,0xd9, + 0x53,0x37,0x71,0x4d,0x38,0x3c,0x3a,0x36,0x34,0x32,0x32,0x35,0x3c,0x46,0x4a,0x4a, + 0x46,0x41,0x3f,0x41,0x46,0x4e,0x54,0x55,0x52,0x53,0x55,0x56,0x55,0x57,0x54,0x4e, + 0x75,0x79,0x7c,0x84,0xa0,0xb6,0xb9,0xb6,0xa6,0xa1,0xa2,0xa1,0xa4,0xa4,0x9e,0x96, + 0x83,0x6e,0x5b,0x4b,0x4b,0x53,0x76,0xa1,0xbd,0xc0,0xb1,0x8a,0x42,0x1b,0x1a,0x1b, + 0x1a,0x19,0x19,0x19,0x1d,0x38,0x58,0x72,0x82,0x86,0x85,0x83,0x85,0x89,0x8d,0x8e, + 0x8c,0x8b,0x8c,0x8e,0x92,0x97,0x9c,0xa3,0xa7,0xab,0xb0,0xb2,0xaa,0x99,0x7c,0x55, + 0x77,0x7e,0x3b,0x86,0xcf,0xcc,0xcd,0xcb,0xcc,0xcd,0xce,0xce,0xca,0x97,0x89,0x4e, + 0x12,0x16,0x2f,0x5e,0x52,0x4c,0x44,0x41,0x3b,0x2e,0xa9,0xd1,0xd0,0xcf,0xce,0xd9, + 0x53,0x37,0x6f,0x4a,0x36,0x3a,0x36,0x35,0x34,0x34,0x36,0x3b,0x41,0x46,0x49,0x49, + 0x42,0x3e,0x3f,0x41,0x46,0x4e,0x57,0x56,0x52,0x53,0x55,0x53,0x57,0x58,0x55,0x51, + 0x76,0x7a,0x7e,0x82,0x98,0xb0,0xb4,0xb5,0xa9,0xa4,0xa4,0xa3,0xa2,0xa2,0x9c,0x91, + 0x7c,0x64,0x55,0x4b,0x4b,0x4d,0x67,0x8d,0xa4,0xa9,0x94,0x5e,0x2c,0x26,0x29,0x27, + 0x25,0x22,0x20,0x1f,0x1d,0x2f,0x54,0x6f,0x80,0x86,0x87,0x86,0x86,0x86,0x86,0x87, + 0x88,0x89,0x8d,0x8d,0x91,0x96,0x9d,0xa2,0xa7,0xab,0xae,0xb2,0xae,0xa0,0x85,0x62, + 0x78,0x7a,0x3c,0x88,0xcf,0xcc,0xcd,0xcd,0xce,0xcd,0xcd,0xcd,0xce,0xd1,0xcb,0xcd, + 0x83,0x13,0x15,0x58,0xce,0xce,0xce,0xca,0xcc,0xcb,0xcc,0xd2,0xcf,0xcf,0xcf,0xd9, + 0x54,0x36,0x71,0x4b,0x35,0x36,0x36,0x38,0x38,0x3a,0x3e,0x44,0x49,0x4d,0x4b,0x47, + 0x43,0x41,0x41,0x41,0x47,0x4e,0x54,0x52,0x50,0x50,0x4e,0x4c,0x4f,0x4f,0x4e,0x4d, + 0x7b,0x7f,0x82,0x86,0x8e,0xa2,0xab,0xb1,0xaa,0xa7,0xa5,0xa3,0xa1,0xa1,0x9a,0x8e, + 0x7a,0x63,0x4f,0x4b,0x48,0x4a,0x56,0x71,0x81,0x7c,0x67,0x4e,0x49,0x54,0x61,0x5f, + 0x5d,0x59,0x51,0x47,0x36,0x3a,0x53,0x6f,0x81,0x8a,0x8b,0x89,0x88,0x85,0x85,0x86, + 0x87,0x8a,0x8d,0x8e,0x91,0x97,0x9c,0xa1,0xa6,0xaa,0xae,0xb1,0xaf,0xa6,0x90,0x74, + 0x7d,0x77,0x3a,0x89,0xcd,0xcc,0xcd,0xcd,0xcd,0xcb,0xcd,0xcd,0xcd,0xcf,0xd6,0x9a, + 0x50,0x16,0x0f,0x12,0x45,0xbe,0xcf,0xce,0xce,0xce,0xce,0xcf,0xcf,0xcf,0xcf,0xd9, + 0x54,0x34,0x73,0x49,0x2f,0x32,0x35,0x3d,0x41,0x45,0x49,0x4d,0x4c,0x50,0x4e,0x4b, + 0x48,0x46,0x46,0x44,0x49,0x50,0x53,0x52,0x4f,0x4c,0x49,0x47,0x48,0x46,0x44,0x46, + 0x7f,0x82,0x86,0x88,0x86,0x97,0xa4,0xa9,0xa9,0xa7,0xa3,0xa2,0xa2,0xa1,0x99,0x92, + 0x7c,0x61,0x46,0x3c,0x40,0x42,0x47,0x4b,0x57,0x5c,0x4e,0x50,0x5d,0x69,0x75,0x78, + 0x79,0x73,0x6d,0x62,0x56,0x55,0x61,0x75,0x82,0x8b,0x8c,0x8a,0x89,0x88,0x89,0x87, + 0x88,0x8a,0x8e,0x8f,0x92,0x97,0x9b,0xa1,0xa3,0xaa,0xb2,0xb6,0xb6,0xab,0x98,0x81, + 0x7e,0x76,0x39,0x8a,0xce,0xcc,0xcc,0xcc,0xcd,0xca,0xce,0xcd,0xcd,0x9c,0x23,0x16, + 0x17,0x19,0x81,0x28,0x18,0x1d,0xb6,0xd0,0xcf,0xce,0xce,0xcf,0xcf,0xce,0xcf,0xd7, + 0x53,0x33,0x70,0x47,0x2a,0x31,0x39,0x3e,0x45,0x49,0x4d,0x52,0x51,0x52,0x4e,0x4d, + 0x4b,0x48,0x4a,0x4a,0x4e,0x50,0x51,0x51,0x4d,0x48,0x46,0x44,0x43,0x40,0x3f,0x40, + 0x81,0x84,0x87,0x8c,0x87,0x8e,0x97,0xa0,0xa4,0xa3,0xa3,0xa1,0xa2,0xa0,0x9c,0x95, + 0x85,0x6c,0x4a,0x35,0x34,0x35,0x3e,0x46,0x51,0x54,0x4d,0x58,0x6a,0x7c,0x8a,0x8d, + 0x8e,0x8a,0x87,0x7f,0x77,0x7f,0x8a,0x92,0x93,0x91,0x8f,0x8e,0x8d,0x8c,0x8b,0x8b, + 0x8c,0x8d,0x90,0x91,0x93,0x96,0x9b,0xa1,0xa6,0xaf,0xb7,0xbd,0xbf,0xbb,0xa6,0x91, + 0x80,0x72,0x3a,0x8d,0xce,0xcc,0xcc,0xcc,0xcd,0xce,0xcd,0xcd,0xc1,0x1f,0x11,0x3f, + 0x92,0xca,0xd3,0xcb,0x62,0x07,0x7f,0xd2,0xce,0xce,0xce,0xce,0xcf,0xce,0xce,0xd8, + 0x53,0x37,0x6d,0x48,0x2f,0x37,0x3c,0x41,0x48,0x4f,0x51,0x56,0x56,0x53,0x53,0x51, + 0x4f,0x4f,0x52,0x55,0x54,0x56,0x55,0x4f,0x4d,0x48,0x46,0x41,0x3b,0x3a,0x38,0x35, + 0x82,0x83,0x88,0x8c,0x8d,0x9f,0xa7,0xa5,0xa4,0xa5,0xa1,0xa0,0xa0,0x9f,0x9d,0x98, + 0x8b,0x73,0x57,0x2a,0x18,0x1c,0x2b,0x40,0x44,0x44,0x42,0x4c,0x63,0x76,0x85,0x89, + 0x89,0x88,0x89,0x86,0x93,0x9e,0xa7,0xaf,0xaf,0xa5,0x9c,0x93,0x90,0x8d,0x8e,0x8e, + 0x8f,0x90,0x91,0x91,0x93,0x96,0x9d,0xa1,0xa6,0xb2,0xbc,0xc4,0xc7,0xc3,0xb7,0xa1, + 0x83,0x72,0x38,0x91,0xce,0xcb,0xcb,0xcc,0xcc,0xcd,0xcb,0xcc,0xbe,0x3c,0xa4,0xd5, + 0xcd,0xce,0xcc,0xce,0xd3,0x91,0x86,0xd2,0xce,0xcf,0xcf,0xcd,0xce,0xcd,0xce,0xd8, + 0x53,0x34,0x6a,0x4a,0x35,0x3c,0x3f,0x46,0x4e,0x50,0x51,0x54,0x53,0x55,0x56,0x57, + 0x56,0x58,0x58,0x57,0x56,0x59,0x56,0x51,0x4e,0x49,0x47,0x3e,0x3a,0x38,0x35,0x33, + 0x83,0x82,0x82,0x8b,0x9c,0xb7,0xb8,0xb1,0xa5,0xa3,0xa2,0xa0,0x9f,0x9f,0x9c,0x9a, + 0x91,0x7e,0x62,0x48,0x20,0x1f,0x30,0x37,0x39,0x36,0x34,0x3f,0x4e,0x65,0x76,0x7b, + 0x7a,0x7c,0x7a,0x7f,0x95,0xad,0xb9,0xbf,0xc1,0xba,0xb2,0xae,0xaa,0xa2,0x96,0x91, + 0x93,0x93,0x93,0x94,0x94,0x98,0x9d,0xa1,0xa5,0xb5,0xc3,0xcc,0xd1,0xcc,0xc4,0xb8, + 0x85,0x6e,0x39,0x93,0xcd,0xcb,0xcc,0xcc,0xcc,0xcc,0xcc,0xcc,0xce,0xda,0xdf,0xdc, + 0xdc,0xd6,0xd5,0xd3,0xd0,0xd1,0xc6,0xcd,0xcd,0xcd,0xcc,0xce,0xce,0xce,0xce,0xd8, + 0x53,0x36,0x69,0x4c,0x36,0x3d,0x42,0x47,0x4d,0x4d,0x4f,0x53,0x55,0x54,0x58,0x5d, + 0x5e,0x5e,0x5e,0x5d,0x5c,0x59,0x58,0x52,0x51,0x4f,0x4b,0x45,0x3d,0x39,0x35,0x32, + 0x84,0x80,0x7e,0x86,0xa5,0xc4,0xc5,0xbe,0xaa,0xa2,0xa2,0xa2,0x9f,0x9c,0x9b,0x93, + 0x91,0x83,0x6a,0x59,0x50,0x3b,0x34,0x36,0x34,0x2f,0x27,0x2d,0x40,0x5b,0x6b,0x74, + 0x76,0x76,0x79,0x88,0x9e,0xb8,0xca,0xd1,0xd4,0xd0,0xd1,0xcf,0xc8,0xb8,0xa8,0x99, + 0x92,0x95,0x95,0x95,0x94,0x95,0x9a,0x9e,0xa0,0xb4,0xc8,0xd3,0xd9,0xd5,0xcb,0xc5, + 0x84,0x6d,0x39,0x92,0xcc,0xca,0xca,0xca,0xcb,0xcb,0xcc,0xca,0xbd,0x27,0x20,0x2a, + 0x37,0x47,0x56,0x5f,0x70,0x7a,0xb1,0xd0,0xac,0xa1,0xc9,0xcc,0xcd,0xcd,0xcd,0xd8, + 0x53,0x36,0x6a,0x4f,0x37,0x3c,0x3f,0x42,0x45,0x47,0x4d,0x53,0x55,0x55,0x59,0x5e, + 0x5f,0x5f,0x60,0x61,0x5f,0x5b,0x58,0x55,0x54,0x50,0x4e,0x47,0x41,0x3a,0x36,0x33, + 0x86,0x83,0x80,0x84,0xa0,0xcc,0xcd,0xc5,0xaa,0xa1,0xa3,0xa2,0x9e,0x9b,0x98,0x90, + 0x7d,0x7b,0x70,0x67,0x66,0x5e,0x45,0x3a,0x32,0x27,0x1f,0x1f,0x31,0x4a,0x5d,0x6b, + 0x71,0x79,0x8f,0xab,0xc2,0xd1,0xdd,0xe1,0xe2,0xe1,0xe2,0xe4,0xdc,0xd2,0xbc,0xa1, + 0x8f,0x8e,0x8e,0x8b,0x87,0x8f,0x97,0x97,0x9b,0xb1,0xc5,0xd2,0xdc,0xd9,0xd1,0xd1, + 0x86,0x6a,0x38,0x91,0xcb,0xc9,0xca,0xca,0xcb,0xcb,0xcb,0xca,0xbf,0x25,0x15,0x0e, + 0x09,0x06,0x08,0x0a,0x08,0x06,0x80,0xd6,0x51,0x06,0xa9,0xcf,0xcd,0xcd,0xcd,0xd6, + 0x54,0x34,0x6b,0x52,0x37,0x3b,0x3b,0x3c,0x3f,0x41,0x48,0x4f,0x52,0x55,0x5a,0x5e, + 0x5d,0x5e,0x62,0x60,0x5f,0x5c,0x58,0x54,0x53,0x4e,0x4b,0x44,0x40,0x39,0x38,0x36, + 0x88,0x87,0x8a,0x87,0x9c,0xc5,0xc9,0xc1,0xa4,0x9e,0xa3,0xa2,0x9e,0x9c,0x96,0x8d, + 0x78,0x62,0x70,0x73,0x6f,0x72,0x69,0x48,0x2a,0x1d,0x1c,0x1c,0x20,0x38,0x49,0x5a, + 0x64,0x73,0xa2,0xc6,0xda,0xe5,0xe8,0xea,0xea,0xe9,0xe9,0xeb,0xea,0xdf,0xc7,0xa7, + 0x84,0x7f,0x82,0x82,0x82,0x89,0x8d,0x8c,0x94,0xad,0xc3,0xd2,0xda,0xdb,0xd6,0xd8, + 0x89,0x66,0x39,0x92,0xca,0xc9,0xcb,0xcc,0xcc,0xcc,0xcb,0xcb,0xcd,0xdd,0xdb,0xd6, + 0xce,0xc8,0xbb,0xae,0xa5,0x9a,0xb4,0xd0,0x95,0x69,0xbb,0xce,0xcd,0xcd,0xcd,0xd6, + 0x55,0x37,0x68,0x50,0x33,0x32,0x32,0x35,0x38,0x3f,0x43,0x44,0x4a,0x51,0x55,0x58, + 0x58,0x57,0x5b,0x5b,0x5b,0x5c,0x59,0x55,0x51,0x4c,0x47,0x42,0x40,0x39,0x38,0x39, + 0x86,0x8a,0x8e,0x8a,0x92,0xb1,0xb8,0xb6,0xa4,0x9f,0xa3,0xa2,0x9d,0x9b,0x94,0x8c, + 0x7d,0x66,0x59,0x66,0x77,0x79,0x6e,0x61,0x3f,0x21,0x1d,0x18,0x19,0x23,0x33,0x45, + 0x54,0x6d,0xae,0xd4,0xe8,0xf1,0xf0,0xef,0xee,0xec,0xec,0xee,0xec,0xe1,0xcb,0xaa, + 0x86,0x7a,0x7d,0x7f,0x82,0x83,0x82,0x82,0x90,0xa9,0xc0,0xd0,0xd7,0xda,0xd9,0xdd, + 0x8e,0x62,0x39,0x97,0xcb,0xc9,0xca,0xca,0xca,0xca,0xcb,0xcb,0xc0,0x7a,0x84,0x96, + 0xa6,0xb3,0xc0,0xc8,0xcf,0xd1,0xd0,0xcd,0xcb,0xcd,0xcb,0xcd,0xcd,0xcd,0xcd,0xd4, + 0x56,0x38,0x67,0x4f,0x2d,0x2b,0x2d,0x2a,0x2f,0x37,0x3a,0x3d,0x42,0x47,0x4c,0x4d, + 0x4e,0x4e,0x54,0x55,0x5b,0x59,0x57,0x51,0x4d,0x49,0x41,0x40,0x3e,0x38,0x39,0x3d, + 0x84,0x8a,0x8d,0x88,0x85,0x9b,0xa8,0xab,0x9f,0x9f,0xa1,0xa0,0x9c,0x98,0x92,0x89, + 0x7f,0x6d,0x5b,0x50,0x65,0x77,0x70,0x64,0x5a,0x3c,0x1b,0x18,0x1a,0x24,0x33,0x41, + 0x4a,0x63,0xb1,0xdb,0xeb,0xf3,0xf3,0xf0,0xec,0xea,0xea,0xed,0xec,0xe1,0xcd,0xac, + 0x85,0x78,0x7d,0x81,0x84,0x82,0x7d,0x7a,0x8b,0xa5,0xbc,0xcb,0xd4,0xd7,0xd7,0xe0, + 0x90,0x5c,0x38,0x98,0xc9,0xc9,0xca,0xca,0xcb,0xcc,0xca,0xc9,0xb9,0x11,0x04,0x04, + 0x04,0x06,0x07,0x0c,0x0d,0x13,0x85,0xcf,0xcc,0xcc,0xce,0xcc,0xcd,0xcc,0xcd,0xd5, + 0x55,0x37,0x64,0x4f,0x27,0x27,0x23,0x22,0x26,0x2e,0x34,0x38,0x3c,0x3e,0x40,0x40, + 0x43,0x44,0x4d,0x52,0x56,0x53,0x50,0x4b,0x48,0x41,0x3c,0x3b,0x3b,0x3a,0x39,0x3f, + 0x88,0x8c,0x8e,0x8a,0x80,0x86,0x8f,0x9c,0x9e,0xa3,0xa5,0xa4,0x9e,0x9a,0x94,0x8b, + 0x82,0x70,0x5b,0x4a,0x41,0x57,0x6f,0x6f,0x61,0x55,0x3c,0x1d,0x26,0x35,0x3c,0x41, + 0x4c,0x69,0xb1,0xdb,0xec,0xf2,0xf1,0xee,0xec,0xe9,0xea,0xed,0xec,0xe1,0xd0,0xae, + 0x8b,0x85,0x8a,0x8e,0x8f,0x88,0x7f,0x79,0x88,0xa2,0xb8,0xc7,0xd0,0xd3,0xd4,0xe0, + 0x94,0x5c,0x37,0x9b,0xca,0xc9,0xca,0xca,0xcb,0xc9,0xcb,0xc9,0xc7,0xa6,0x94,0x82, + 0x74,0x66,0x54,0x30,0x1c,0x16,0x8c,0xcc,0xcb,0xcd,0xcc,0xcc,0xcb,0xcc,0xcc,0xd5, + 0x55,0x36,0x63,0x51,0x26,0x24,0x1f,0x20,0x24,0x27,0x2d,0x33,0x38,0x36,0x35,0x35, + 0x37,0x3f,0x4a,0x4e,0x4f,0x4d,0x48,0x43,0x40,0x3e,0x39,0x39,0x3a,0x3b,0x3f,0x46, + 0x8c,0x8e,0x8e,0x8e,0x84,0x8e,0x97,0x9b,0x9e,0xa4,0xa7,0xa6,0xa0,0x99,0x94,0x8f, + 0x84,0x6d,0x52,0x3c,0x31,0x34,0x4e,0x6b,0x6c,0x60,0x51,0x45,0x3c,0x4c,0x58,0x5d, + 0x60,0x7c,0xb8,0xdc,0xee,0xf2,0xf2,0xed,0xeb,0xe9,0xea,0xee,0xec,0xe0,0xcc,0xac, + 0x90,0x8e,0x90,0x8e,0x8d,0x84,0x79,0x76,0x83,0x9c,0xb3,0xc1,0xca,0xcf,0xd3,0xe0, + 0x93,0x5c,0x38,0x9e,0xcb,0xc9,0xca,0xca,0xca,0xca,0xca,0xcb,0xcb,0xcd,0xca,0xd0, + 0xd0,0xd3,0xd4,0xd1,0x66,0x17,0xa6,0xcd,0xcb,0xcc,0xcb,0xcd,0xcb,0xcc,0xcd,0xd5, + 0x56,0x35,0x63,0x53,0x26,0x21,0x20,0x23,0x24,0x26,0x2b,0x31,0x35,0x31,0x2f,0x2e, + 0x2f,0x38,0x41,0x45,0x46,0x45,0x41,0x3b,0x38,0x38,0x34,0x34,0x34,0x39,0x3f,0x4a, + 0x92,0x91,0x93,0x8f,0x90,0xa5,0xaa,0xa6,0x9f,0xa2,0xa5,0xa8,0xa3,0x9c,0x98,0x91, + 0x83,0x66,0x47,0x2d,0x23,0x28,0x2c,0x49,0x6b,0x67,0x5b,0x5b,0x66,0x73,0x7a,0x7a, + 0x6c,0x7e,0xbc,0xde,0xec,0xef,0xed,0xec,0xeb,0xeb,0xed,0xf1,0xe9,0xde,0xc6,0xa4, + 0x8c,0x8e,0x8f,0x8c,0x87,0x7e,0x75,0x71,0x82,0x99,0xad,0xbd,0xc6,0xce,0xd3,0xdd, + 0x91,0x5a,0x38,0xa0,0xcb,0xc9,0xca,0xca,0xca,0xca,0xca,0xca,0xca,0xc9,0xcd,0xcc, + 0xcc,0xcb,0xca,0xca,0x9a,0x0b,0x5a,0xd5,0xcb,0xcc,0xcb,0xcc,0xcb,0xcc,0xce,0xd5, + 0x56,0x34,0x67,0x54,0x29,0x23,0x20,0x24,0x26,0x27,0x2c,0x31,0x33,0x31,0x2f,0x2b, + 0x2c,0x33,0x39,0x3a,0x3c,0x38,0x33,0x2e,0x2d,0x2f,0x2f,0x2d,0x2d,0x37,0x3f,0x4b, + 0x93,0x91,0x90,0x8d,0x96,0xbb,0xc0,0xb8,0xa6,0xa0,0xa7,0xa9,0xa4,0x9e,0x9a,0x92, + 0x7f,0x63,0x3d,0x20,0x17,0x1a,0x24,0x30,0x50,0x69,0x69,0x71,0x7d,0x84,0x83,0x7b, + 0x71,0x7f,0xbd,0xda,0xe4,0xe6,0xe4,0xe1,0xe3,0xe5,0xea,0xee,0xe7,0xdb,0xbf,0x9b, + 0x8b,0x8e,0x8c,0x87,0x83,0x7a,0x71,0x6e,0x81,0x9a,0xac,0xbd,0xc9,0xcf,0xd1,0xd5, + 0x8a,0x56,0x38,0xa0,0xc9,0xc8,0xc9,0xc9,0xc9,0xc9,0xc8,0xc8,0xc1,0x68,0x6b,0x77, + 0x89,0x95,0xa4,0x98,0x30,0x11,0x75,0xd1,0xcb,0xcc,0xcb,0xca,0xcb,0xcb,0xcc,0xd5, + 0x56,0x35,0x66,0x57,0x2a,0x28,0x25,0x26,0x27,0x29,0x2c,0x30,0x32,0x2f,0x2b,0x29, + 0x2a,0x2e,0x2f,0x2f,0x2f,0x29,0x24,0x23,0x26,0x29,0x2a,0x29,0x2a,0x32,0x3d,0x45, + 0x95,0x90,0x8f,0x8c,0x99,0xc6,0xce,0xc7,0xab,0x9a,0xa4,0xa5,0xa2,0x9c,0x98,0x91, + 0x7f,0x61,0x3a,0x1a,0x10,0x13,0x1c,0x2d,0x40,0x5b,0x6b,0x76,0x86,0x8f,0x8d,0x7c, + 0x6f,0x86,0xbc,0xd4,0xe1,0xdc,0xcf,0xc7,0xc7,0xd2,0xe3,0xe8,0xe6,0xd8,0xba,0x94, + 0x8a,0x88,0x89,0x83,0x7f,0x7a,0x72,0x71,0x89,0xa2,0xb4,0xc0,0xcb,0xce,0xc8,0xc8, + 0x89,0x53,0x37,0xa4,0xc8,0xc9,0xc8,0xc9,0xc9,0xc9,0xca,0xc8,0xba,0x1b,0x0f,0x0c, + 0x09,0x08,0x06,0x0d,0x0f,0x31,0xc0,0xcb,0xcc,0xcb,0xcb,0xcc,0xcb,0xcb,0xcb,0xd5, + 0x57,0x37,0x64,0x54,0x2d,0x2e,0x2b,0x29,0x2b,0x2b,0x2e,0x2f,0x2f,0x2c,0x29,0x25, + 0x23,0x26,0x29,0x28,0x23,0x1d,0x1d,0x1f,0x1f,0x21,0x22,0x23,0x28,0x30,0x3c,0x45, + 0x92,0x93,0x91,0x8e,0x97,0xc3,0xcd,0xc5,0xad,0x98,0x9d,0xa0,0x9e,0x98,0x94,0x91, + 0x80,0x60,0x3b,0x1b,0x10,0x0e,0x14,0x26,0x3d,0x4d,0x5a,0x75,0x7b,0x7e,0x83,0x6b, + 0x51,0x79,0xad,0xc0,0xc8,0xc3,0xa6,0x8f,0x96,0xb5,0xd6,0xe5,0xe4,0xd8,0xbb,0x94, + 0x87,0x86,0x82,0x82,0x7f,0x79,0x71,0x7e,0x95,0xab,0xbc,0xc7,0xca,0xc4,0xba,0xb3, + 0x88,0x4f,0x36,0xa6,0xc9,0xc8,0xc9,0xc8,0xc9,0xc9,0xc9,0xca,0xc4,0xb1,0xa8,0x9d, + 0x90,0x83,0x71,0x67,0x91,0xc5,0xca,0xca,0xca,0xca,0xcb,0xcb,0xcb,0xcb,0xcb,0xd5, + 0x56,0x38,0x61,0x56,0x2c,0x2f,0x2e,0x2d,0x2e,0x2d,0x2f,0x31,0x33,0x2e,0x28,0x23, + 0x1e,0x1b,0x1c,0x1b,0x18,0x12,0x15,0x19,0x19,0x18,0x1a,0x1d,0x21,0x2a,0x38,0x40, + 0x94,0x92,0x92,0x8e,0x91,0xb1,0xbb,0xb8,0xa4,0x93,0x96,0x99,0x9a,0x93,0x92,0x8f, + 0x7e,0x5f,0x3c,0x1e,0x12,0x11,0x16,0x27,0x39,0x45,0x47,0x5d,0x64,0x5d,0x5f,0x50, + 0x3a,0x57,0x93,0xa8,0xaa,0x9b,0x71,0x51,0x54,0x95,0xc9,0xde,0xde,0xd3,0xb8,0x9e, + 0x8d,0x88,0x84,0x83,0x81,0x7c,0x7e,0x93,0xa3,0xb3,0xc2,0xc8,0xc2,0xb8,0xa7,0x97, + 0x87,0x4e,0x33,0xa8,0xc8,0xc8,0xc9,0xc8,0xc7,0xc7,0xc8,0xd0,0xc8,0xc9,0xca,0xd0, + 0xd2,0xd1,0xd5,0xd0,0xcc,0xc9,0xcb,0xca,0xc9,0xca,0xca,0xcb,0xcb,0xcb,0xcb,0xd5, + 0x56,0x36,0x5e,0x58,0x2c,0x30,0x31,0x32,0x32,0x32,0x33,0x34,0x35,0x30,0x2a,0x24, + 0x1c,0x17,0x14,0x13,0x13,0x10,0x13,0x15,0x16,0x15,0x1a,0x20,0x20,0x2a,0x32,0x3b, + 0x96,0x91,0x90,0x8c,0x89,0x94,0xa6,0xa5,0x96,0x8d,0x90,0x95,0x98,0x94,0x91,0x8c, + 0x7b,0x60,0x40,0x24,0x15,0x18,0x1f,0x28,0x31,0x3b,0x34,0x36,0x42,0x46,0x46,0x3f, + 0x37,0x34,0x57,0x79,0x83,0x75,0x56,0x41,0x4d,0x90,0xb9,0xcf,0xd8,0xcd,0xb8,0x9f, + 0x8d,0x8a,0x87,0x86,0x85,0x87,0x94,0xa2,0xac,0xb9,0xc0,0xc0,0xb4,0xa4,0x8f,0x79, + 0x8d,0x4b,0x34,0xa9,0xc8,0xc8,0xc8,0xc7,0xc8,0xce,0xad,0x37,0xda,0xc9,0xa4,0x4e, + 0x4e,0x5e,0x6c,0x7e,0xbe,0xcc,0xca,0xc9,0xc9,0xc9,0xc9,0xca,0xca,0xca,0xca,0xd4, + 0x56,0x35,0x64,0x55,0x2b,0x2f,0x34,0x38,0x37,0x34,0x33,0x36,0x37,0x35,0x2c,0x24, + 0x1d,0x16,0x12,0x10,0x11,0x0f,0x11,0x12,0x14,0x15,0x1a,0x23,0x27,0x2c,0x31,0x37, + 0x9b,0x93,0x8d,0x8c,0x89,0x89,0x91,0x94,0x8d,0x8d,0x91,0x99,0x9d,0x9f,0x99,0x91, + 0x7b,0x5e,0x3c,0x21,0x1a,0x21,0x2c,0x34,0x34,0x30,0x27,0x1d,0x24,0x31,0x33,0x3a, + 0x39,0x35,0x2f,0x34,0x3a,0x35,0x3d,0x48,0x5e,0x8e,0xad,0xc0,0xc6,0xbc,0xa7,0x91, + 0x8b,0x8a,0x89,0x88,0x8d,0x96,0xa2,0xac,0xb4,0xb6,0xb6,0xad,0x9d,0x87,0x6e,0x57, + 0x91,0x4a,0x34,0xab,0xc7,0xc8,0xc7,0xc8,0xc7,0x7e,0x0f,0x12,0xd6,0x5d,0x0c,0x0b, + 0x0a,0x06,0x03,0x08,0x0d,0x5e,0xc7,0xc9,0xc9,0xc9,0xc9,0xca,0xca,0xca,0xca,0xd3, + 0x55,0x34,0x64,0x53,0x27,0x2e,0x32,0x37,0x37,0x36,0x37,0x38,0x36,0x32,0x2a,0x24, + 0x1c,0x14,0x11,0x12,0x10,0x11,0x10,0x10,0x11,0x14,0x1b,0x24,0x2d,0x32,0x34,0x35, + 0x9d,0x94,0x8c,0x8d,0x8d,0x97,0xa0,0x9d,0x94,0x90,0x93,0x9d,0xa4,0xa3,0x9f,0x94, + 0x7b,0x5d,0x37,0x21,0x22,0x2f,0x3d,0x41,0x3e,0x30,0x21,0x1c,0x22,0x27,0x33,0x4e, + 0x52,0x48,0x3e,0x2c,0x30,0x55,0x67,0x6c,0x6b,0x7b,0x8f,0x9b,0xa3,0x97,0x89,0x86, + 0x8a,0x8a,0x88,0x8d,0x96,0x9d,0xa6,0xaf,0xb0,0xae,0xa6,0x94,0x7c,0x66,0x4c,0x46, + 0x92,0x47,0x31,0xad,0xc8,0xc6,0xc7,0xc8,0xc9,0x29,0x15,0x98,0xaf,0x1a,0x18,0x8b, + 0xbf,0xb2,0xa4,0x81,0x1b,0x15,0x9a,0xca,0xc8,0xca,0xc9,0xc8,0xca,0xca,0xcb,0xd3, + 0x53,0x36,0x65,0x51,0x26,0x2d,0x32,0x35,0x36,0x39,0x36,0x33,0x31,0x2c,0x28,0x21, + 0x1c,0x13,0x10,0x10,0x10,0x10,0x0f,0x0f,0x12,0x13,0x1c,0x23,0x2d,0x33,0x37,0x35, + 0x98,0x97,0x8e,0x90,0x98,0xb2,0xbb,0xb2,0x9d,0x92,0x97,0xa0,0xa8,0xa9,0xa6,0x99, + 0x79,0x5a,0x36,0x23,0x28,0x39,0x46,0x47,0x42,0x32,0x21,0x1e,0x27,0x41,0x53,0x68, + 0x5b,0x56,0x59,0x43,0x52,0x5d,0x63,0x68,0x68,0x65,0x69,0x76,0x75,0x73,0x79,0x80, + 0x83,0x83,0x87,0x8c,0x96,0x9f,0xaa,0xad,0xa5,0x9c,0x8c,0x74,0x58,0x42,0x37,0x48, + 0x92,0x46,0x31,0xaf,0xc8,0xc7,0xc7,0xc8,0xc8,0x12,0x23,0xca,0xac,0x19,0x45,0xc9, + 0xc9,0xc8,0xc9,0xcf,0x90,0x0b,0x56,0xd1,0xc8,0xc8,0xc8,0xca,0xc9,0xca,0xc9,0xd3, + 0x54,0x31,0x61,0x54,0x25,0x2b,0x31,0x33,0x32,0x34,0x31,0x2e,0x2b,0x29,0x25,0x1e, + 0x1b,0x14,0x13,0x12,0x12,0x12,0x10,0x10,0x11,0x12,0x1a,0x23,0x2d,0x33,0x37,0x33, + 0x93,0x96,0x90,0x93,0xa0,0xc7,0xcf,0xc6,0xaf,0x92,0x98,0xa2,0xaa,0xaa,0xa6,0x98, + 0x79,0x56,0x33,0x20,0x2a,0x39,0x44,0x45,0x40,0x31,0x1f,0x22,0x36,0x5a,0x75,0x89, + 0x7e,0x66,0x52,0x44,0x4e,0x5a,0x60,0x65,0x65,0x56,0x55,0x56,0x5a,0x64,0x70,0x7b, + 0x7f,0x82,0x87,0x8d,0x9c,0xa8,0xa9,0xa3,0x98,0x88,0x70,0x53,0x3a,0x33,0x34,0x4e, + 0x94,0x43,0x32,0xaf,0xc5,0xc5,0xc6,0xc6,0xc6,0x32,0x17,0xa0,0xb8,0x29,0x34,0xbb, + 0xcb,0xca,0xcc,0xc7,0x8d,0x0d,0x82,0xcc,0xc7,0xc7,0xc8,0xc9,0xc9,0xc5,0xc9,0xd2, + 0x54,0x33,0x5e,0x50,0x21,0x28,0x2d,0x30,0x2e,0x2f,0x2c,0x2a,0x26,0x25,0x1f,0x19, + 0x16,0x14,0x14,0x13,0x13,0x13,0x11,0x11,0x11,0x13,0x1b,0x22,0x2a,0x30,0x32,0x2e, + 0x93,0x95,0x90,0x90,0x9f,0xcf,0xde,0xd3,0xb8,0x94,0x98,0xa3,0xa9,0xaa,0xa4,0x93, + 0x77,0x54,0x30,0x1e,0x22,0x30,0x3c,0x3d,0x34,0x2c,0x1e,0x20,0x3a,0x6a,0x7e,0x7d, + 0x7c,0x68,0x4d,0x3e,0x44,0x49,0x4a,0x4b,0x3b,0x3a,0x43,0x4e,0x5d,0x6d,0x7a,0x7f, + 0x81,0x86,0x90,0x98,0xa4,0xaa,0xa3,0x9e,0x96,0x83,0x63,0x4b,0x3b,0x37,0x36,0x53, + 0x94,0x44,0x31,0xb2,0xc5,0xc5,0xc6,0xc6,0xc6,0x8c,0x09,0x1b,0x31,0x20,0x13,0x2b, + 0x44,0x50,0x5b,0x56,0x22,0x22,0xaf,0xc9,0xc9,0xc8,0xc7,0xc7,0xc9,0xc7,0xc9,0xd3, + 0x56,0x33,0x5f,0x50,0x20,0x27,0x2e,0x30,0x2f,0x30,0x2c,0x27,0x24,0x21,0x1a,0x16, + 0x16,0x17,0x15,0x15,0x14,0x14,0x14,0x13,0x12,0x13,0x1a,0x21,0x29,0x2d,0x2e,0x2d, + 0x95,0x92,0x8e,0x8c,0x9f,0xc9,0xd6,0xd3,0xb9,0x99,0x9c,0xa4,0xa7,0xa5,0xa0,0x92, + 0x77,0x54,0x2f,0x1c,0x1d,0x24,0x2d,0x31,0x36,0x33,0x2c,0x29,0x34,0x65,0x83,0x7c, + 0x6b,0x54,0x3d,0x37,0x33,0x2c,0x22,0x1f,0x2e,0x3e,0x4d,0x5c,0x6d,0x79,0x7e,0x82, + 0x88,0x90,0x9b,0xa1,0xac,0xae,0xab,0xaa,0xa3,0x83,0x64,0x57,0x4c,0x3e,0x37,0x56, + 0x96,0x40,0x2f,0xb4,0xc5,0xc5,0xc6,0xc6,0xc6,0xcb,0x97,0x3e,0x27,0x22,0x1b,0x1b, + 0x17,0x12,0x11,0x10,0x0e,0x07,0x86,0xcc,0xc9,0xc8,0xc9,0xca,0xca,0xca,0xc9,0xd2, + 0x57,0x34,0x60,0x54,0x22,0x28,0x2f,0x32,0x32,0x31,0x2e,0x29,0x24,0x21,0x1b,0x17, + 0x16,0x15,0x16,0x14,0x15,0x15,0x14,0x14,0x13,0x15,0x1b,0x22,0x28,0x2b,0x2d,0x2c, + 0x98,0x92,0x8b,0x85,0x91,0xb4,0xc4,0xc2,0xaf,0x99,0x9f,0xa2,0xa6,0xa3,0x9e,0x93, + 0x7c,0x59,0x38,0x20,0x1b,0x1d,0x23,0x35,0x49,0x46,0x3c,0x32,0x31,0x4d,0x69,0x74, + 0x62,0x3d,0x27,0x22,0x1f,0x1f,0x21,0x2e,0x3e,0x4c,0x5d,0x6d,0x79,0x7c,0x82,0x87, + 0x91,0x9b,0xa5,0xaa,0xb3,0xb7,0xba,0xb6,0xa3,0x85,0x6d,0x65,0x59,0x44,0x40,0x5a, + 0x95,0x3f,0x2e,0xb5,0xc5,0xc5,0xc5,0xc5,0xc6,0xc6,0xc7,0xc5,0xc0,0xbe,0xbe,0xba, + 0xb9,0xb4,0xac,0xa3,0x97,0x8c,0xad,0xc9,0xc8,0xc7,0xc4,0xc9,0xca,0xca,0xca,0xd1, + 0x57,0x34,0x60,0x57,0x24,0x2a,0x2f,0x31,0x32,0x33,0x30,0x29,0x23,0x20,0x1b,0x17, + 0x14,0x13,0x13,0x14,0x15,0x15,0x15,0x14,0x15,0x17,0x1c,0x21,0x28,0x2c,0x2b,0x29, + 0x9b,0x92,0x8a,0x89,0x8c,0x9f,0xaa,0xab,0x9c,0x94,0x9e,0xa1,0xa5,0xa2,0x9d,0x92, + 0x7e,0x5f,0x3e,0x26,0x1d,0x1e,0x2c,0x40,0x4d,0x50,0x47,0x3c,0x3c,0x47,0x4e,0x4c, + 0x3f,0x27,0x1e,0x20,0x21,0x23,0x32,0x3e,0x4d,0x5d,0x6b,0x78,0x7d,0x81,0x85,0x8e, + 0x98,0xa1,0xab,0xb0,0xb8,0xbe,0xbc,0xb1,0xa3,0x8c,0x7b,0x6e,0x59,0x47,0x46,0x61, + 0x95,0x3d,0x2d,0xb6,0xc5,0xc5,0xc5,0xc5,0xc6,0xc6,0xc6,0xc6,0xc4,0xc5,0xc5,0xc6, + 0xc5,0xc5,0xc7,0xc6,0xc8,0xc8,0xc8,0xc7,0xc7,0xc8,0xc7,0xca,0xc8,0xc9,0xc8,0xd1, + 0x57,0x35,0x5e,0x5c,0x28,0x2e,0x30,0x31,0x31,0x33,0x31,0x2a,0x24,0x20,0x1b,0x15, + 0x13,0x13,0x13,0x13,0x14,0x15,0x15,0x14,0x16,0x19,0x1c,0x1f,0x25,0x29,0x28,0x27, + 0x96,0x8c,0x87,0x88,0x8c,0x93,0x96,0x94,0x8e,0x8f,0x98,0xa1,0xa5,0xa3,0x9d,0x93, + 0x7e,0x63,0x42,0x2b,0x23,0x23,0x2f,0x3f,0x4c,0x48,0x48,0x3b,0x37,0x46,0x52,0x51, + 0x3d,0x26,0x1f,0x1f,0x21,0x32,0x3f,0x4c,0x5c,0x6d,0x77,0x7c,0x80,0x85,0x8d,0x95, + 0x9e,0xa6,0xae,0xb4,0xb8,0xba,0xb3,0xa8,0x9c,0x89,0x7a,0x5f,0x4b,0x47,0x47,0x5f, + 0x97,0x3a,0x2c,0xb6,0xc5,0xc4,0xc4,0xc4,0xc5,0xc5,0xc4,0xc5,0xc6,0xc5,0xc5,0xc5, + 0xc7,0xc6,0xc6,0xc5,0xc7,0xc8,0xc7,0xc8,0xc7,0xc8,0xc9,0xc8,0xc8,0xc8,0xc8,0xd1, + 0x56,0x34,0x5a,0x5d,0x2a,0x2f,0x30,0x31,0x30,0x2e,0x2e,0x29,0x22,0x1d,0x19,0x14, + 0x12,0x12,0x12,0x13,0x14,0x14,0x14,0x15,0x15,0x19,0x19,0x1d,0x21,0x24,0x25,0x23, + 0x94,0x8a,0x84,0x85,0x8f,0xa3,0xac,0xa3,0x92,0x8e,0x98,0xa0,0xa5,0xa5,0x9f,0x94, + 0x7e,0x61,0x45,0x30,0x27,0x27,0x2a,0x34,0x3d,0x3d,0x32,0x2b,0x37,0x4e,0x56,0x50, + 0x3c,0x2a,0x21,0x23,0x32,0x3e,0x4a,0x59,0x6b,0x76,0x78,0x7f,0x84,0x8a,0x94,0x9e, + 0xa4,0xab,0xaf,0xb3,0xb5,0xb2,0xaa,0x9f,0x8f,0x79,0x5e,0x47,0x42,0x42,0x41,0x5a, + 0x9b,0x3c,0x2e,0xb7,0xc4,0xc4,0xc5,0xc4,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc7, + 0xc7,0xc7,0xc7,0xc7,0xc7,0xc8,0xc8,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc8,0xd0, + 0x55,0x33,0x5b,0x60,0x2a,0x2d,0x2d,0x2d,0x2a,0x29,0x29,0x25,0x21,0x1b,0x18,0x13, + 0x11,0x11,0x12,0x13,0x14,0x14,0x14,0x15,0x16,0x17,0x1a,0x1b,0x1e,0x20,0x22,0x22, + 0x91,0x86,0x80,0x87,0x92,0xb0,0xbb,0xb3,0xa1,0x8e,0x96,0xa0,0xa7,0xa8,0x9f,0x94, + 0x7e,0x61,0x46,0x30,0x24,0x23,0x26,0x29,0x28,0x27,0x20,0x25,0x39,0x52,0x58,0x4e, + 0x3e,0x2e,0x28,0x35,0x3f,0x4a,0x59,0x67,0x74,0x7a,0x7e,0x83,0x87,0x8f,0x98,0xa2, + 0xa9,0xad,0xb0,0xb0,0xaf,0xad,0xa6,0x96,0x7c,0x60,0x4a,0x44,0x46,0x44,0x42,0x56, + 0x97,0x3c,0x2e,0xb9,0xc5,0xc4,0xc5,0xc4,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc5,0xc4, + 0xc5,0xc7,0xc7,0xc6,0xc6,0xc6,0xc6,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xc7,0xd0, + 0x55,0x31,0x5a,0x5e,0x2a,0x2c,0x2d,0x2d,0x2a,0x29,0x27,0x24,0x1f,0x1a,0x18,0x13, + 0x11,0x11,0x14,0x14,0x17,0x16,0x17,0x17,0x15,0x13,0x16,0x17,0x19,0x1b,0x20,0x21, + 0x8a,0x82,0x80,0x84,0x92,0xb6,0xc7,0xc2,0xb0,0x8e,0x94,0x9e,0xa8,0xa7,0x9e,0x92, + 0x7e,0x61,0x44,0x2f,0x23,0x20,0x22,0x23,0x1f,0x1b,0x1a,0x26,0x3d,0x50,0x55,0x4f, + 0x40,0x30,0x33,0x3f,0x4b,0x59,0x67,0x72,0x77,0x7d,0x82,0x84,0x84,0x8e,0x9d,0xa8, + 0xaf,0xaf,0xaf,0xaf,0xb0,0xae,0xa4,0x94,0x7a,0x57,0x4c,0x4d,0x4c,0x4b,0x4b,0x5b, + 0x97,0x3b,0x2f,0xba,0xc4,0xc4,0xc5,0xc4,0xc4,0xc5,0xc5,0xc5,0xc5,0xc6,0xc5,0xc7, + 0xc5,0xc4,0xc9,0xc7,0xc7,0xc6,0xc7,0xc6,0xc7,0xc7,0xc8,0xc7,0xc6,0xc7,0xc7,0xd0, + 0x57,0x32,0x5b,0x60,0x2a,0x2c,0x2c,0x2f,0x2a,0x28,0x27,0x23,0x1e,0x1b,0x1a,0x15, + 0x13,0x13,0x13,0x15,0x1b,0x1c,0x1c,0x1a,0x17,0x14,0x15,0x14,0x16,0x1c,0x20,0x21, + 0x81,0x82,0x83,0x8b,0x91,0xb3,0xcd,0xc5,0xb3,0x8e,0x92,0x9e,0xa4,0xa3,0x9a,0x8e, + 0x7a,0x62,0x44,0x2b,0x20,0x1a,0x1a,0x1b,0x1b,0x1a,0x1a,0x24,0x38,0x49,0x51,0x4b, + 0x3e,0x37,0x45,0x52,0x5c,0x67,0x73,0x78,0x7e,0x82,0x85,0x82,0x80,0x8b,0x9d,0xa7, + 0xaf,0xaf,0xaf,0xaf,0xb2,0xaf,0xa6,0x95,0x7f,0x6a,0x55,0x4e,0x4c,0x4b,0x49,0x59, + 0x97,0x39,0x30,0xbb,0xc4,0xc3,0xc4,0xc4,0xc4,0xc0,0xc4,0xc4,0xc4,0xc5,0xc5,0xc0, + 0xc6,0xbb,0x9b,0xc2,0xc5,0xc6,0xc6,0xc6,0xc7,0xc7,0xc8,0xc7,0xc7,0xc7,0xc7,0xcf, + 0x57,0x33,0x58,0x61,0x29,0x2c,0x2f,0x2f,0x2a,0x28,0x26,0x22,0x1e,0x1d,0x19,0x16, + 0x14,0x13,0x16,0x1d,0x23,0x24,0x24,0x1f,0x1d,0x18,0x16,0x16,0x18,0x1d,0x21,0x22, + 0x7d,0x80,0x82,0x84,0x8b,0xa8,0xbd,0xba,0xae,0x8e,0x90,0x9b,0xa3,0xa0,0x96,0x8b, + 0x7b,0x64,0x4c,0x2f,0x21,0x1a,0x18,0x18,0x19,0x18,0x18,0x1e,0x2f,0x41,0x46,0x46, + 0x43,0x44,0x50,0x5c,0x6a,0x76,0x78,0x7e,0x82,0x85,0x87,0x85,0x83,0x8d,0x9a,0xa4, + 0xad,0xaf,0xaf,0xb0,0xb3,0xb2,0xaa,0x9d,0x8c,0x79,0x6c,0x5d,0x5a,0x59,0x59,0x5b, + 0x95,0x39,0x31,0xc0,0xc2,0xc3,0xc4,0xc3,0xc5,0xc5,0xc5,0xc5,0xc5,0xc4,0xc5,0xc5, + 0x9b,0x26,0x17,0x7c,0xc8,0xc5,0xc5,0xc5,0xc6,0xc6,0xc8,0xc8,0xc4,0xc4,0xc8,0xcf, + 0x58,0x33,0x56,0x5c,0x25,0x2a,0x2c,0x2f,0x30,0x2c,0x29,0x26,0x20,0x1d,0x19,0x16, + 0x15,0x16,0x1b,0x21,0x26,0x28,0x27,0x22,0x21,0x1b,0x19,0x1a,0x1d,0x20,0x23,0x23, + 0x7c,0x7e,0x7d,0x82,0x84,0x94,0xa9,0xa7,0x9c,0x88,0x8f,0x98,0xa2,0x9f,0x97,0x8c, + 0x79,0x67,0x50,0x37,0x24,0x1a,0x18,0x18,0x18,0x17,0x16,0x18,0x26,0x35,0x3b,0x46, + 0x4b,0x50,0x5c,0x69,0x73,0x7e,0x7d,0x83,0x83,0x82,0x84,0x87,0x89,0x91,0x9e,0xa6, + 0xae,0xae,0xaf,0xae,0xaf,0xae,0xad,0xa5,0x93,0x84,0x78,0x71,0x61,0x5e,0x6b,0x66, + 0x95,0x3a,0x33,0xbf,0xc4,0xc3,0xc4,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc4,0xc5,0x8c, + 0x17,0x17,0x16,0x14,0x80,0xca,0xc6,0xc5,0xc6,0xc6,0xc7,0xc6,0xc6,0xc7,0xc7,0xcd, + 0x59,0x33,0x52,0x5c,0x24,0x29,0x2b,0x2f,0x30,0x2e,0x2b,0x28,0x25,0x20,0x1c,0x17, + 0x16,0x1a,0x1e,0x21,0x2a,0x2b,0x2b,0x28,0x25,0x22,0x1f,0x20,0x21,0x21,0x22,0x23, + 0x7f,0x7d,0x78,0x7a,0x80,0x81,0x8f,0x91,0x84,0x7d,0x88,0x97,0xa1,0x9f,0x99,0x8c, + 0x7c,0x6b,0x54,0x3b,0x29,0x1b,0x18,0x17,0x17,0x16,0x15,0x14,0x19,0x2a,0x3d,0x48, + 0x53,0x59,0x68,0x73,0x7a,0x82,0x83,0x87,0x88,0x87,0x87,0x87,0x8c,0x98,0xa7,0xaf, + 0xaf,0xb1,0xaf,0xad,0xac,0xa7,0xa4,0xa6,0x9d,0x8c,0x88,0x81,0x7b,0x81,0x8c,0x78, + 0x98,0x38,0x31,0xc0,0xc3,0xc3,0xc4,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc9,0x73,0x1c, + 0x16,0x16,0x16,0x16,0x15,0x5f,0xc6,0xc5,0xc6,0xc6,0xc7,0xc6,0xc6,0xc6,0xc7,0xce, + 0x59,0x35,0x57,0x5c,0x21,0x28,0x2c,0x2e,0x30,0x2d,0x2b,0x28,0x27,0x23,0x20,0x1d, + 0x1a,0x1c,0x1f,0x26,0x2d,0x2f,0x2e,0x2d,0x29,0x26,0x25,0x24,0x23,0x21,0x21,0x22, + 0x80,0x7d,0x77,0x7b,0x7f,0x86,0x89,0x8a,0x7f,0x7d,0x88,0x95,0x9e,0x9d,0x96,0x8e, + 0x7d,0x6f,0x58,0x41,0x2d,0x1c,0x16,0x13,0x14,0x15,0x15,0x16,0x15,0x1e,0x36,0x48, + 0x59,0x62,0x69,0x72,0x78,0x7e,0x85,0x8a,0x8a,0x87,0x8d,0x92,0x91,0x9a,0xaa,0xb3, + 0xb1,0xb2,0xaf,0xab,0xa8,0xa2,0x9b,0x9b,0x9f,0x96,0x96,0x9c,0xa2,0xa5,0xa0,0x7b, + 0x99,0x35,0x31,0xbf,0xc2,0xc3,0xc4,0xc3,0xc2,0xc3,0xc3,0xc2,0xc3,0x5d,0x0d,0x15, + 0x17,0x14,0x15,0x17,0x15,0x18,0x66,0xc8,0xc5,0xc5,0xc7,0xc7,0xc6,0xc6,0xc5,0xce, + 0x59,0x36,0x5a,0x5e,0x21,0x27,0x2c,0x2f,0x2e,0x2c,0x2c,0x2b,0x2b,0x2a,0x2a,0x25, + 0x22,0x1f,0x22,0x27,0x2d,0x2f,0x2e,0x2d,0x2b,0x2a,0x2a,0x2a,0x27,0x23,0x22,0x22, + 0x80,0x7f,0x7c,0x7f,0x85,0x90,0xa0,0x9e,0x92,0x88,0x8c,0x97,0x9c,0x9c,0x96,0x8f, + 0x7e,0x70,0x5b,0x41,0x2e,0x1c,0x12,0x0e,0x11,0x15,0x16,0x16,0x18,0x18,0x2d,0x4b, + 0x63,0x71,0x74,0x78,0x7b,0x7b,0x7d,0x88,0x90,0x8e,0x8b,0x8d,0x9a,0xa3,0xaa,0xb4, + 0xb4,0xb3,0xaf,0xa9,0xa3,0x9f,0x97,0x94,0x99,0xa1,0xa9,0xb2,0xbb,0xc3,0xbb,0x85, + 0x99,0x33,0x2f,0xc1,0xc2,0xc2,0xc2,0xc2,0xc3,0xc1,0xc1,0xc4,0x31,0x16,0x17,0x16, + 0x16,0x15,0x15,0x17,0x1a,0x16,0x14,0x4c,0xc1,0xc6,0xc5,0xc6,0xc6,0xc5,0xc5,0xcc, + 0x5a,0x34,0x59,0x62,0x22,0x29,0x2d,0x2e,0x2e,0x2e,0x2e,0x31,0x33,0x32,0x2f,0x2b, + 0x29,0x25,0x27,0x29,0x2c,0x2d,0x2d,0x2d,0x2b,0x2e,0x2e,0x2e,0x2b,0x28,0x25,0x25, + 0x81,0x84,0x81,0x81,0x85,0x94,0xae,0xaf,0xa5,0x93,0x92,0x9a,0x9b,0x9c,0x98,0x8d, + 0x81,0x71,0x5f,0x45,0x2d,0x1c,0x12,0x0f,0x12,0x16,0x19,0x19,0x18,0x18,0x2b,0x4b, + 0x64,0x74,0x79,0x7a,0x75,0x76,0x74,0x79,0x87,0x94,0x94,0x8e,0x91,0xa3,0xaf,0xb2, + 0xb5,0xb5,0xad,0xa6,0xa1,0x9c,0x96,0x93,0x96,0xa3,0xb3,0xbd,0xc9,0xcd,0xc7,0x84, + 0x9a,0x33,0x31,0xc2,0xc2,0xc1,0xc1,0xc1,0xc0,0xc7,0xa6,0x29,0x14,0x16,0x18,0x16, + 0x16,0x15,0x16,0x17,0x16,0x15,0x15,0x12,0x49,0xc2,0xc5,0xc5,0xc5,0xc5,0xc6,0xcb, + 0x59,0x33,0x58,0x65,0x24,0x2a,0x2d,0x2e,0x2e,0x2f,0x2f,0x35,0x39,0x37,0x35,0x30, + 0x2c,0x26,0x27,0x28,0x29,0x2b,0x2c,0x2c,0x2d,0x2e,0x2e,0x2d,0x2a,0x27,0x26,0x24, + 0x81,0x84,0x85,0x84,0x84,0x95,0xbb,0xbc,0xb4,0x9c,0x94,0x9a,0x9e,0x9e,0x9a,0x91, + 0x84,0x71,0x5e,0x46,0x30,0x20,0x16,0x13,0x19,0x22,0x26,0x24,0x1e,0x16,0x28,0x49, + 0x62,0x74,0x79,0x77,0x79,0x78,0x73,0x71,0x6e,0x78,0x87,0x95,0x96,0xa3,0xb0,0xb6, + 0xb5,0xb5,0xad,0xa6,0x9f,0x98,0x94,0x95,0x96,0xa1,0xb4,0xc6,0xd1,0xd1,0xcb,0x81, + 0x98,0x35,0x31,0xc2,0xc0,0xc0,0xc0,0xc3,0xc2,0x7d,0x10,0x17,0x17,0x16,0x15,0x17, + 0x15,0x15,0x16,0x16,0x16,0x16,0x14,0x15,0x15,0x3f,0xc6,0xc4,0xc3,0xc6,0xc5,0xca, + 0x59,0x32,0x54,0x65,0x26,0x29,0x2c,0x2e,0x2e,0x2d,0x2f,0x37,0x3d,0x3a,0x38,0x33, + 0x2d,0x25,0x27,0x29,0x28,0x27,0x29,0x27,0x2a,0x2f,0x2d,0x2d,0x2a,0x25,0x23,0x20, + 0x80,0x83,0x87,0x84,0x84,0x8e,0xb7,0xbb,0xb9,0xa2,0x92,0x98,0x9f,0xa0,0x9d,0x94, + 0x85,0x71,0x59,0x43,0x30,0x1f,0x17,0x19,0x27,0x36,0x3d,0x38,0x2c,0x1d,0x22,0x43, + 0x5c,0x6b,0x73,0x73,0x78,0x7e,0x7e,0x79,0x70,0x67,0x64,0x7c,0x94,0xa2,0xb2,0xbc, + 0xb9,0xb5,0xad,0xa3,0x9b,0x96,0x93,0x94,0x95,0xa0,0xb6,0xc9,0xd3,0xd2,0xc4,0x73, + 0x99,0x31,0x32,0xc4,0xbf,0xbf,0xc0,0xc0,0x4c,0x17,0x16,0x15,0x19,0x17,0x16,0x15, + 0x15,0x15,0x15,0x15,0x15,0x15,0x16,0x14,0x16,0x11,0x2e,0xb8,0xc4,0xc3,0xc4,0xc9, + 0x58,0x32,0x50,0x63,0x23,0x29,0x2a,0x2d,0x2e,0x2d,0x33,0x39,0x38,0x37,0x32,0x2d, + 0x27,0x25,0x25,0x27,0x26,0x26,0x27,0x26,0x2c,0x2d,0x2e,0x29,0x27,0x22,0x1f,0x1d, + 0x7e,0x7e,0x82,0x84,0x88,0x8a,0xa6,0xb2,0xb0,0xa1,0x93,0x95,0x9b,0x9e,0x9e,0x99, + 0x86,0x73,0x5d,0x47,0x33,0x20,0x1a,0x2a,0x3b,0x4f,0x5b,0x55,0x47,0x33,0x2b,0x3c, + 0x52,0x5e,0x68,0x6b,0x70,0x7f,0x87,0x87,0x7e,0x6f,0x62,0x64,0x82,0x9c,0xb2,0xbf, + 0xbd,0xb6,0xad,0xa3,0x9a,0x96,0x97,0x96,0x97,0xa8,0xbb,0xcb,0xd4,0xcf,0xbc,0x63, + 0x9a,0x31,0x32,0xc5,0xbf,0xbf,0xba,0x41,0x13,0x13,0x16,0x15,0x15,0x16,0x15,0x16, + 0x15,0x14,0x15,0x15,0x14,0x15,0x16,0x17,0x18,0x14,0x16,0x2d,0xbd,0xc5,0xc4,0xc9, + 0x58,0x34,0x52,0x64,0x21,0x28,0x2a,0x2d,0x2f,0x30,0x35,0x38,0x35,0x32,0x2b,0x24, + 0x1c,0x1d,0x21,0x24,0x25,0x26,0x28,0x2a,0x2f,0x2d,0x2b,0x25,0x22,0x1e,0x1a,0x1c, + 0x7c,0x7a,0x7e,0x81,0x88,0x85,0x91,0x9d,0x9f,0x98,0x94,0x93,0x96,0x9a,0x9a,0x94, + 0x86,0x75,0x5e,0x47,0x35,0x24,0x28,0x39,0x4e,0x61,0x70,0x6e,0x5e,0x44,0x39,0x3b, + 0x4b,0x59,0x5a,0x57,0x68,0x7c,0x87,0x87,0x84,0x7f,0x6e,0x5e,0x75,0x93,0xad,0xbb, + 0xbf,0xb6,0xad,0xa2,0x9a,0x97,0x96,0x95,0x9d,0xb1,0xc3,0xcf,0xd5,0xcc,0xb6,0x5a, + 0x99,0x32,0x32,0xc8,0xbd,0xaf,0x2a,0x14,0x16,0x15,0x16,0x15,0x14,0x14,0x15,0x15, + 0x15,0x15,0x15,0x14,0x14,0x14,0x16,0x14,0x14,0x14,0x14,0x12,0x29,0xb4,0xc1,0xcb, + 0x59,0x34,0x52,0x65,0x21,0x28,0x2a,0x2d,0x30,0x30,0x37,0x37,0x34,0x2d,0x28,0x20, + 0x1a,0x1a,0x1e,0x24,0x26,0x29,0x2b,0x2e,0x30,0x2c,0x28,0x21,0x1e,0x1d,0x1e,0x20, + 0x7d,0x7a,0x7d,0x81,0x87,0x87,0x88,0x8d,0x91,0x90,0x90,0x91,0x90,0x95,0x92,0x90, + 0x83,0x72,0x5e,0x49,0x39,0x31,0x3a,0x4e,0x61,0x70,0x78,0x77,0x6a,0x51,0x44,0x43, + 0x48,0x51,0x55,0x52,0x5b,0x6a,0x7f,0x8a,0x87,0x7d,0x74,0x6f,0x72,0x8e,0xab,0xbd, + 0xc1,0xba,0xae,0xa2,0x99,0x96,0x98,0x9d,0xac,0xbd,0xcb,0xd6,0xd7,0xca,0xaf,0x55, + 0x9a,0x32,0x34,0xc2,0x93,0x19,0x12,0x16,0x14,0x15,0x15,0x15,0x16,0x1c,0x17,0x17, + 0x17,0x17,0x18,0x17,0x16,0x15,0x17,0x13,0x13,0x13,0x14,0x16,0x14,0x1d,0xad,0xca, + 0x5b,0x36,0x51,0x69,0x23,0x29,0x2b,0x2f,0x32,0x33,0x36,0x33,0x2f,0x27,0x1f,0x1b, + 0x18,0x1c,0x20,0x26,0x2a,0x2f,0x30,0x32,0x33,0x2d,0x27,0x21,0x1e,0x20,0x25,0x25, + 0x7e,0x7e,0x7f,0x7e,0x86,0x85,0x87,0x8e,0x8c,0x8d,0x8d,0x8f,0x8b,0x8c,0x8b,0x8a, + 0x82,0x75,0x69,0x58,0x4a,0x47,0x4f,0x5f,0x6d,0x75,0x74,0x70,0x67,0x57,0x4a,0x45, + 0x46,0x4c,0x4e,0x4e,0x56,0x5e,0x66,0x77,0x7f,0x7a,0x71,0x70,0x78,0x93,0xae,0xbf, + 0xc0,0xbe,0xaf,0xa1,0x97,0x97,0x9a,0xa5,0xb4,0xc5,0xcf,0xd9,0xda,0xc7,0xa7,0x4d, + 0x97,0x32,0x35,0xc3,0x51,0x12,0x15,0x14,0x19,0x13,0x14,0x16,0x1b,0x15,0x15,0x15, + 0x14,0x14,0x14,0x15,0x15,0x16,0x16,0x13,0x14,0x14,0x13,0x15,0x15,0x13,0x1a,0xb2, + 0x5b,0x33,0x51,0x6c,0x25,0x2b,0x2c,0x2e,0x34,0x34,0x35,0x31,0x2b,0x24,0x1e,0x1c, + 0x1d,0x20,0x24,0x29,0x2d,0x32,0x32,0x32,0x2f,0x29,0x24,0x1f,0x20,0x21,0x28,0x27, + 0x80,0x85,0x84,0x82,0x85,0x86,0x8e,0x94,0x92,0x8f,0x8b,0x8b,0x89,0x87,0x8a,0x89, + 0x85,0x7f,0x75,0x67,0x5c,0x57,0x61,0x6a,0x70,0x6f,0x69,0x60,0x57,0x51,0x47,0x45, + 0x40,0x42,0x43,0x42,0x49,0x56,0x60,0x5f,0x61,0x65,0x62,0x68,0x75,0x93,0xb2,0xc4, + 0xc5,0xc0,0xb3,0xa2,0x9a,0x9c,0x9f,0xa8,0xb9,0xca,0xd7,0xdd,0xda,0xc3,0x9f,0x46, + 0x96,0x31,0x35,0xc7,0xc6,0x57,0x10,0x17,0x13,0x10,0x30,0x35,0x12,0x14,0x14,0x14, + 0x13,0x13,0x13,0x13,0x13,0x14,0x14,0x14,0x15,0x13,0x13,0x15,0x14,0x14,0x16,0x18, + 0x43,0x34,0x4f,0x71,0x2b,0x30,0x30,0x31,0x34,0x32,0x32,0x2f,0x2a,0x23,0x1d,0x1f, + 0x20,0x24,0x28,0x2d,0x30,0x33,0x32,0x31,0x2d,0x24,0x1f,0x1d,0x20,0x25,0x2b,0x2c, + 0x7e,0x86,0x85,0x81,0x7f,0x83,0x8f,0x97,0x95,0x91,0x89,0x8b,0x8e,0x8c,0x8a,0x8a, + 0x86,0x84,0x7b,0x70,0x67,0x61,0x60,0x62,0x63,0x5c,0x52,0x4a,0x45,0x40,0x3b,0x3a, + 0x39,0x35,0x35,0x34,0x35,0x3d,0x4c,0x51,0x4a,0x40,0x45,0x4f,0x5d,0x77,0x9e,0xb5, + 0xb8,0xb6,0xad,0x9d,0x94,0x92,0x98,0xa9,0xba,0xc8,0xd4,0xd9,0xd0,0xb5,0x8f,0x3c, + 0x96,0x2f,0x36,0xc7,0xbe,0xc0,0x61,0x11,0x17,0x40,0xc3,0x49,0x14,0x14,0x14,0x15, + 0x14,0x14,0x10,0x13,0x13,0x15,0x16,0x14,0x13,0x17,0x14,0x18,0x13,0x14,0x13,0x12, + 0x11,0x2a,0x50,0x72,0x31,0x35,0x34,0x32,0x32,0x31,0x2e,0x2c,0x27,0x22,0x20,0x20, + 0x22,0x28,0x2b,0x30,0x31,0x32,0x31,0x2d,0x27,0x22,0x1c,0x1e,0x21,0x26,0x29,0x29, + 0x80,0x8b,0x85,0x7e,0x7a,0x7f,0x88,0x91,0x93,0x92,0x8d,0x8f,0x91,0x93,0x8f,0x8b, + 0x87,0x7d,0x77,0x6d,0x64,0x5b,0x52,0x50,0x4c,0x44,0x3d,0x36,0x35,0x34,0x34,0x30, + 0x31,0x2b,0x29,0x26,0x29,0x2a,0x2f,0x33,0x31,0x2c,0x2a,0x2d,0x40,0x58,0x79,0x91, + 0x97,0x96,0x8d,0x85,0x7c,0x7c,0x83,0x96,0xa3,0xaf,0xb9,0xc0,0xb7,0xa1,0x87,0x44, + 0x95,0x2f,0x36,0xc6,0xbd,0xbc,0xc6,0x7a,0x64,0xbe,0xbd,0x45,0x13,0x14,0x15,0x15, + 0x14,0x12,0x16,0x13,0x14,0x15,0x14,0x15,0x14,0x38,0x99,0x17,0x17,0x16,0x15,0x12, + 0x17,0x30,0x51,0x76,0x37,0x39,0x36,0x35,0x35,0x35,0x30,0x2b,0x25,0x21,0x1f,0x20, + 0x24,0x28,0x2c,0x2f,0x32,0x33,0x2e,0x28,0x25,0x1e,0x1b,0x1c,0x21,0x26,0x28,0x24, + 0x83,0x87,0x82,0x7c,0x7b,0x81,0x82,0x8a,0x90,0x8e,0x8e,0x90,0x93,0x95,0x90,0x8d, + 0x84,0x77,0x6e,0x63,0x59,0x4f,0x45,0x3c,0x36,0x31,0x2d,0x2f,0x2e,0x2c,0x2d,0x2b, + 0x29,0x25,0x21,0x1e,0x1f,0x21,0x25,0x25,0x23,0x21,0x20,0x26,0x2d,0x3e,0x4a,0x5b, + 0x64,0x63,0x65,0x62,0x65,0x69,0x64,0x6c,0x77,0x83,0x8f,0x98,0x9a,0x99,0x94,0x59, + 0x92,0x2f,0x35,0xc6,0xbc,0xbd,0xbc,0xbf,0xbf,0xbe,0xb9,0x3e,0x14,0x14,0x14,0x14, + 0x13,0x13,0x14,0x13,0x14,0x14,0x15,0x14,0x14,0x40,0xc4,0x9a,0x16,0x12,0x16,0x36, + 0x5a,0x31,0x4a,0x7a,0x3b,0x3c,0x38,0x36,0x36,0x37,0x31,0x2b,0x24,0x20,0x1d,0x1d, + 0x21,0x27,0x2a,0x2f,0x32,0x32,0x2c,0x28,0x23,0x1d,0x1a,0x1a,0x1d,0x21,0x20,0x1f, + 0x7f,0x85,0x7f,0x7c,0x7f,0x83,0x84,0x85,0x87,0x89,0x8c,0x8d,0x90,0x91,0x8f,0x8a, + 0x81,0x75,0x68,0x5e,0x59,0x52,0x44,0x37,0x2d,0x2d,0x2f,0x2f,0x30,0x31,0x31,0x2f, + 0x2c,0x27,0x1f,0x1a,0x1b,0x1b,0x1d,0x21,0x1f,0x21,0x27,0x33,0x38,0x40,0x3d,0x36, + 0x3d,0x47,0x51,0x63,0x67,0x62,0x58,0x4b,0x44,0x47,0x51,0x69,0x8a,0x9e,0xac,0x60, + 0x90,0x2f,0x36,0xc6,0xbc,0xbc,0xbd,0xbc,0xbd,0xbd,0xb6,0x1e,0x15,0x14,0x13,0x13, + 0x13,0x13,0x13,0x13,0x13,0x17,0x13,0x14,0x14,0x4f,0xbf,0xc3,0xa7,0x1a,0x44,0xc6, + 0x58,0x31,0x48,0x79,0x3a,0x3c,0x37,0x39,0x37,0x37,0x31,0x2c,0x27,0x24,0x24,0x2a, + 0x31,0x3b,0x43,0x47,0x46,0x3f,0x30,0x26,0x20,0x1a,0x18,0x19,0x1a,0x1c,0x1b,0x1a, + 0x77,0x7f,0x79,0x7a,0x7d,0x85,0x83,0x82,0x81,0x88,0x8a,0x87,0x8d,0x8c,0x8b,0x89, + 0x7f,0x74,0x6a,0x61,0x60,0x5a,0x4a,0x3b,0x33,0x32,0x34,0x34,0x35,0x36,0x38,0x39, + 0x31,0x29,0x21,0x1b,0x1b,0x19,0x1a,0x1c,0x20,0x23,0x31,0x3f,0x44,0x49,0x48,0x3c, + 0x42,0x56,0x70,0x85,0x8a,0x81,0x6f,0x59,0x44,0x33,0x35,0x61,0x8d,0xa8,0xba,0x63, + 0x8e,0x30,0x36,0xc5,0xbd,0xbc,0xbc,0xbc,0xbc,0xbc,0xb6,0x26,0x13,0x14,0x13,0x13, + 0x13,0x13,0x13,0x13,0x13,0x16,0x14,0x14,0x13,0x57,0xbf,0xc3,0xc1,0xaa,0xc6,0xc1, + 0x59,0x31,0x49,0x77,0x37,0x3a,0x3a,0x39,0x39,0x37,0x36,0x36,0x39,0x41,0x4e,0x62, + 0x6e,0x76,0x7d,0x7d,0x72,0x5e,0x40,0x27,0x1e,0x19,0x19,0x18,0x19,0x19,0x18,0x17, + 0x77,0x7c,0x79,0x76,0x7c,0x7f,0x80,0x7d,0x7d,0x82,0x87,0x89,0x8a,0x8a,0x8b,0x8a, + 0x81,0x78,0x6f,0x69,0x6a,0x65,0x53,0x41,0x37,0x39,0x3c,0x3e,0x3c,0x39,0x39,0x37, + 0x35,0x31,0x28,0x22,0x21,0x22,0x25,0x2a,0x2e,0x32,0x3e,0x4b,0x51,0x52,0x51,0x43, + 0x4b,0x64,0x86,0x9f,0xa6,0xa0,0x92,0x7b,0x64,0x48,0x3e,0x5f,0x91,0xae,0xc1,0x60, + 0x92,0x2f,0x38,0xc4,0xbc,0xbc,0xbc,0xbc,0xbc,0xbc,0xb7,0x3a,0x13,0x15,0x15,0x13, + 0x13,0x13,0x13,0x13,0x13,0x14,0x15,0x14,0x15,0x62,0xc0,0xc3,0xc2,0xc4,0xc0,0xc4, + 0x58,0x2f,0x48,0x7f,0x48,0x4a,0x4a,0x4b,0x4f,0x53,0x55,0x52,0x5f,0x6f,0x7a,0x8a, + 0x94,0x96,0x96,0x8c,0x7c,0x63,0x48,0x2c,0x20,0x19,0x18,0x18,0x1a,0x19,0x17,0x17, + 0x76,0x7a,0x79,0x76,0x78,0x7c,0x7c,0x7f,0x7f,0x82,0x83,0x88,0x8a,0x8a,0x8b,0x8b, + 0x84,0x7f,0x73,0x6c,0x6a,0x60,0x4d,0x3f,0x37,0x36,0x3d,0x41,0x3c,0x38,0x38,0x37, + 0x39,0x37,0x36,0x32,0x30,0x37,0x3e,0x44,0x48,0x4a,0x4f,0x5a,0x5d,0x5f,0x59,0x50, + 0x58,0x72,0x96,0xa9,0xb4,0xb7,0xad,0x9b,0x87,0x68,0x57,0x66,0x91,0xaf,0xc5,0x60, + 0x72,0x31,0x39,0xc5,0xba,0xbc,0xbc,0xbc,0xbd,0xb8,0xb9,0x37,0x15,0x14,0x13,0x13, + 0x13,0x13,0x14,0x12,0x13,0x14,0x13,0x15,0x15,0x6a,0xc0,0xc1,0xc0,0xc1,0xc2,0xc6, + 0x5b,0x2f,0x35,0x6d,0x51,0x53,0x54,0x55,0x58,0x5d,0x5c,0x56,0x65,0x77,0x81,0x8f, + 0x94,0x95,0x91,0x84,0x75,0x58,0x39,0x26,0x1f,0x19,0x18,0x17,0x18,0x18,0x18,0x17, + 0x76,0x7c,0x7b,0x75,0x75,0x79,0x7c,0x83,0x80,0x81,0x82,0x86,0x8a,0x8c,0x8c,0x8b, + 0x86,0x89,0x8a,0x88,0x7f,0x6a,0x53,0x42,0x3a,0x3e,0x49,0x52,0x50,0x52,0x52,0x49, + 0x48,0x44,0x42,0x43,0x44,0x4c,0x59,0x5e,0x66,0x68,0x6c,0x70,0x6e,0x70,0x6f,0x66, + 0x66,0x80,0xa0,0xb7,0xc4,0xc7,0xc0,0xb6,0xa6,0x8e,0x76,0x7d,0x9a,0xb4,0xcf,0x5f, + 0x11,0x31,0x39,0xc5,0xba,0xbc,0xbc,0xbc,0xbc,0xbd,0xb9,0x34,0x14,0x13,0x13,0x12, + 0x13,0x12,0x10,0x17,0x14,0x14,0x13,0x16,0x14,0x75,0xbf,0xbf,0xbe,0xbe,0xc3,0xc6, + 0x5a,0x2f,0x14,0x35,0x53,0x53,0x53,0x53,0x55,0x59,0x58,0x53,0x57,0x60,0x6a,0x6f, + 0x6c,0x68,0x64,0x5c,0x4b,0x37,0x27,0x20,0x1d,0x18,0x17,0x17,0x16,0x16,0x15,0x14, + 0x78,0x7b,0x7b,0x7a,0x76,0x78,0x80,0x87,0x86,0x86,0x85,0x86,0x8a,0x8e,0x8d,0x89, + 0x86,0x94,0x9c,0x9d,0x9c,0x8b,0x73,0x63,0x5a,0x60,0x6a,0x73,0x7c,0x86,0x80,0x6f, + 0x67,0x5c,0x52,0x52,0x56,0x5d,0x6b,0x71,0x7b,0x7a,0x7e,0x80,0x83,0x7c,0x7f,0x75, + 0x70,0x8d,0xac,0xc3,0xd1,0xd3,0xce,0xc4,0xbd,0xae,0x9b,0x97,0xa4,0xbf,0xd5,0x40, + 0x13,0x34,0x38,0xc5,0xbb,0xbb,0xbb,0xbb,0xbc,0xbd,0xb8,0x32,0x13,0x13,0x13,0x10, + 0x1d,0x29,0x2a,0x16,0x12,0x13,0x12,0x13,0x15,0x80,0xc0,0xbf,0xbf,0xc0,0xc1,0xc5, + 0x5a,0x2e,0x15,0x2d,0x40,0x3e,0x40,0x3f,0x3d,0x3b,0x39,0x32,0x28,0x25,0x29,0x2a, + 0x25,0x21,0x1f,0x1f,0x1f,0x20,0x1d,0x1a,0x18,0x15,0x16,0x15,0x14,0x14,0x12,0x12, + 0x7b,0x7b,0x7d,0x79,0x77,0x77,0x7e,0x83,0x87,0x87,0x85,0x84,0x87,0x89,0x8c,0x88, + 0x84,0x8f,0x9d,0xa2,0xa4,0x99,0x85,0x78,0x72,0x7a,0x81,0x8d,0x97,0x9e,0x9c,0x8d, + 0x7d,0x70,0x61,0x62,0x65,0x68,0x6e,0x74,0x7a,0x7e,0x7f,0x86,0x87,0x82,0x83,0x7f, + 0x7c,0x93,0xb0,0xc5,0xd1,0xd6,0xd2,0xcc,0xcb,0xc4,0xbc,0xb3,0xb4,0xc3,0xcf,0x19, + 0x14,0x34,0x38,0xc3,0xba,0xba,0xbb,0xbb,0xbb,0xbc,0xb8,0x32,0x13,0x13,0x12,0x37, + 0x1a,0x13,0x15,0x11,0x14,0x13,0x13,0x14,0x14,0x8d,0xbf,0xbf,0xbf,0xc0,0xc1,0xc4, + 0x57,0x2c,0x15,0x24,0x27,0x28,0x28,0x28,0x25,0x22,0x1d,0x1b,0x16,0x15,0x13,0x12, + 0x13,0x16,0x17,0x18,0x19,0x18,0x16,0x16,0x14,0x14,0x13,0x12,0x13,0x13,0x12,0x11, + 0x59,0x5b,0x5d,0x59,0x59,0x5a,0x5e,0x62,0x67,0x68,0x67,0x68,0x6c,0x6e,0x71,0x70, + 0x6d,0x6b,0x73,0x81,0x89,0x81,0x75,0x72,0x73,0x7c,0x88,0x92,0x99,0x9c,0x9b,0x94, + 0x87,0x7a,0x72,0x73,0x75,0x7a,0x80,0x7c,0x79,0x7a,0x7c,0x81,0x83,0x83,0x86,0x89, + 0x82,0x8c,0xa7,0xb4,0xc0,0xc7,0xc6,0xc5,0xc8,0xcc,0xcd,0xcc,0xc8,0xcd,0xb0,0x0d, + 0x17,0x33,0x36,0xc5,0xba,0xba,0xbb,0xbb,0xbb,0xbc,0xb8,0x2f,0x11,0x13,0x13,0x11, + 0x13,0x12,0x13,0x12,0x13,0x13,0x13,0x14,0x11,0x97,0xbd,0xbf,0xbf,0xc0,0xc0,0xc4, + 0x58,0x2a,0x14,0x20,0x26,0x26,0x24,0x26,0x26,0x24,0x1d,0x1a,0x18,0x18,0x17,0x15, + 0x14,0x14,0x14,0x16,0x17,0x15,0x13,0x12,0x12,0x11,0x10,0x11,0x11,0x11,0x11,0x10, + 0x3b,0x3c,0x3c,0x3a,0x38,0x3c,0x38,0x3b,0x3a,0x37,0x35,0x34,0x33,0x33,0x32,0x35, + 0x30,0x35,0x34,0x30,0x2e,0x2f,0x32,0x2f,0x2e,0x2e,0x2d,0x2e,0x2d,0x2d,0x2c,0x31, + 0x32,0x32,0x39,0x38,0x33,0x35,0x39,0x3c,0x3d,0x3b,0x3e,0x40,0x3e,0x45,0x44,0x44, + 0x43,0x41,0x44,0x42,0x45,0x46,0x49,0x51,0x54,0x47,0x24,0x33,0x40,0x2e,0x18,0x10, + 0x15,0x33,0x36,0xc5,0xba,0xba,0xbb,0xbb,0xbb,0xbb,0xb7,0x2e,0x14,0x13,0x13,0x13, + 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x14,0x0f,0xa0,0xbd,0xbf,0xbe,0xbf,0xc0,0xc4, + 0x58,0x26,0x12,0x1f,0x2b,0x28,0x28,0x26,0x26,0x24,0x20,0x1c,0x1b,0x1b,0x1a,0x19, + 0x17,0x17,0x17,0x17,0x17,0x16,0x15,0x13,0x11,0x12,0x13,0x13,0x13,0x13,0x11,0x11, + 0x69,0x65,0x62,0x68,0x6b,0x66,0x6e,0x69,0x64,0x65,0x66,0x6d,0x6a,0x6e,0x6d,0x69, + 0x67,0x68,0x68,0x68,0x63,0x67,0x6c,0x62,0x60,0x68,0x69,0x63,0x65,0x66,0x67,0x67, + 0x67,0x61,0x62,0x5d,0x5a,0x5d,0x5f,0x5c,0x5d,0x5b,0x5c,0x5a,0x57,0x5b,0x59,0x56, + 0x54,0x55,0x52,0x4e,0x4b,0x4c,0x3d,0x3d,0x39,0x3c,0x10,0x0f,0x0e,0x0f,0x0f,0x10, + 0x13,0x31,0x36,0xc4,0xb9,0xb9,0xba,0xbb,0xbb,0xbb,0xb8,0x2b,0x13,0x13,0x13,0x13, + 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0xad,0xbe,0xbe,0xbd,0xbe,0xbf,0xc3, + 0x57,0x26,0x11,0x23,0x28,0x28,0x29,0x26,0x23,0x22,0x22,0x1e,0x1e,0x1e,0x1c,0x1c, + 0x1c,0x1c,0x1c,0x1c,0x1b,0x1a,0x18,0x16,0x16,0x15,0x15,0x16,0x16,0x15,0x13,0x11, + 0x7e,0x77,0x7d,0x76,0x7f,0x8c,0x7b,0x86,0x84,0x87,0x85,0x8d,0x88,0x8c,0x86,0x8e, + 0x8e,0x8d,0x8d,0x8e,0x90,0x90,0x90,0x93,0x8f,0x91,0x8c,0x8f,0x92,0x93,0x93,0x93, + 0x90,0x8e,0x8a,0x87,0x8d,0x8b,0x8e,0x8e,0x8a,0x86,0x87,0x85,0x84,0x82,0x85,0x86, + 0x83,0x83,0x87,0x88,0x7f,0x81,0x7b,0x4c,0x43,0x41,0x39,0x17,0x0e,0x0c,0x0c,0x10, + 0x14,0x31,0x39,0xc3,0xb9,0xb9,0xba,0xba,0xba,0xba,0xb8,0x26,0x13,0x13,0x13,0x13, + 0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0xbb,0xbf,0xbd,0xbd,0xbc,0xbc,0xc3, + 0x56,0x2a,0x10,0x23,0x29,0x28,0x27,0x25,0x21,0x20,0x22,0x1e,0x1e,0x1e,0x1f,0x1f, + 0x20,0x21,0x21,0x22,0x20,0x1c,0x1c,0x1b,0x19,0x18,0x18,0x19,0x19,0x18,0x15,0x14, + 0x85,0x88,0x87,0x86,0x8a,0x87,0x8a,0x92,0x8c,0x8e,0x89,0x88,0x8b,0x80,0x7f,0x7c, + 0x81,0x81,0x84,0x8a,0x8d,0x89,0x8f,0x93,0x94,0x98,0x99,0x91,0x8e,0x8d,0x88,0x94, + 0x8f,0x8a,0x8a,0x85,0x89,0x8d,0x90,0x94,0x92,0x94,0x93,0x93,0x97,0x9a,0x94,0x9a, + 0x94,0x91,0x92,0x93,0x94,0x93,0x95,0x7b,0x4f,0x53,0x4f,0x53,0x23,0x0d,0x0d,0x0e, + 0x13,0x34,0x3b,0xc3,0xb9,0xb9,0xb9,0xb9,0xba,0xb9,0xb3,0x24,0x13,0x12,0x15,0x13, + 0x16,0x14,0x15,0x12,0x12,0x12,0x13,0x14,0x12,0xbe,0xbe,0xbc,0xbd,0xbd,0xbd,0xc2, + 0x55,0x29,0x12,0x1f,0x28,0x27,0x25,0x24,0x20,0x1f,0x20,0x1d,0x1e,0x1f,0x1f,0x20, + 0x22,0x24,0x25,0x27,0x25,0x22,0x1f,0x20,0x1e,0x1d,0x1e,0x1e,0x1e,0x1c,0x19,0x17, + 0x73,0x6f,0x74,0x76,0x75,0x78,0x7d,0x8a,0x91,0x90,0x88,0x82,0x7f,0x82,0x84,0x7f, + 0x79,0x72,0x6a,0x6b,0x6e,0x64,0x52,0x3e,0x38,0x47,0x56,0x61,0x67,0x6a,0x6d,0x76, + 0x79,0x7b,0x7b,0x7e,0x81,0x83,0x83,0x84,0x84,0x86,0x8b,0x8a,0x87,0x84,0x82,0x81, + 0x79,0x75,0x77,0x72,0x75,0x73,0x78,0x79,0x5e,0x5f,0x59,0x44,0x3f,0x32,0x2e,0x10, + 0x14,0x28,0x3d,0xc3,0xb8,0xb9,0xb9,0xb9,0xb8,0xb8,0xb8,0x6d,0x5b,0x54,0x48,0x1a, + 0x13,0x0f,0x0c,0x11,0x12,0x12,0x11,0x0d,0x14,0xc5,0xbe,0xbc,0xbd,0xbd,0xbd,0xc1, + 0x54,0x29,0x10,0x1f,0x25,0x26,0x24,0x22,0x1e,0x1c,0x1b,0x1b,0x1b,0x1c,0x1e,0x1f, + 0x22,0x24,0x26,0x29,0x2a,0x27,0x25,0x24,0x21,0x22,0x22,0x20,0x1e,0x1d,0x1c,0x19, + 0x72,0x72,0x73,0x72,0x70,0x70,0x77,0x85,0x8f,0x8b,0x88,0x81,0x7d,0x80,0x86,0x84, + 0x7b,0x6f,0x6a,0x6d,0x74,0x6b,0x56,0x42,0x3c,0x45,0x54,0x5e,0x63,0x69,0x6f,0x75, + 0x7b,0x7e,0x81,0x83,0x85,0x86,0x88,0x8a,0x8d,0x8f,0x91,0x92,0x94,0x8f,0x87,0x7b, + 0x70,0x63,0x59,0x59,0x66,0x73,0x80,0x8c,0x94,0x96,0x9a,0x9e,0x99,0x8f,0x7d,0x14, + 0x13,0x17,0x42,0xbf,0xb7,0xb6,0xb6,0xb8,0xb7,0xb5,0xb8,0xba,0xb7,0xba,0xb8,0xb1, + 0xaf,0x9d,0x79,0x81,0xae,0xac,0xaa,0x9f,0x9c,0xc0,0xbd,0xbc,0xbc,0xbe,0xbc,0xc1, + 0x55,0x29,0x15,0x1e,0x27,0x27,0x25,0x23,0x1e,0x1c,0x18,0x19,0x1a,0x1b,0x1b,0x1d, + 0x21,0x27,0x2a,0x30,0x2f,0x31,0x2b,0x28,0x26,0x24,0x23,0x22,0x21,0x1e,0x1b,0x1a, + 0x71,0x6f,0x6e,0x67,0x69,0x6a,0x70,0x7a,0x82,0x84,0x82,0x7f,0x81,0x84,0x86,0x85, + 0x7b,0x6f,0x69,0x6c,0x72,0x6c,0x59,0x43,0x3d,0x44,0x51,0x5d,0x63,0x68,0x6d,0x72, + 0x77,0x7d,0x80,0x83,0x86,0x89,0x8b,0x8d,0x8f,0x91,0x93,0x94,0x95,0x91,0x8a,0x81, + 0x7b,0x75,0x6c,0x69,0x73,0x80,0x8a,0x98,0xa2,0xa6,0xab,0xae,0xa9,0x9e,0x90,0x19, + 0x2b,0x20,0x3a,0xc4,0xbc,0xbe,0xbf,0xc0,0xbd,0xbc,0xbc,0xb9,0xba,0xba,0xb9,0xb8, + 0xb7,0xba,0xbc,0xbe,0xba,0xbb,0xbe,0xbc,0xbd,0xbb,0xbd,0xbc,0xbb,0xbc,0xbb,0xc2, + 0x56,0x29,0x14,0x1e,0x29,0x2b,0x27,0x23,0x1f,0x1d,0x1a,0x16,0x17,0x18,0x1a,0x1d, + 0x24,0x2d,0x30,0x33,0x37,0x36,0x32,0x2f,0x2a,0x25,0x24,0x20,0x20,0x1e,0x1a,0x1a, + 0x73,0x6e,0x6c,0x66,0x66,0x68,0x6e,0x71,0x75,0x75,0x79,0x7a,0x80,0x85,0x86,0x80, + 0x77,0x6b,0x69,0x6f,0x72,0x6a,0x53,0x41,0x3a,0x43,0x4f,0x5c,0x63,0x68,0x6b,0x70, + 0x74,0x79,0x81,0x86,0x88,0x8c,0x8f,0x92,0x91,0x93,0x94,0x95,0x94,0x90,0x8a,0x84, + 0x7f,0x80,0x7b,0x78,0x7c,0x89,0x95,0x9d,0xa3,0xab,0xb1,0xb4,0xb2,0xaf,0xa2,0x1e, + 0x85,0x2a,0x2c,0x31,0x3a,0x41,0x4c,0x54,0x61,0x75,0x7f,0x8e,0x9d,0xa5,0xaf,0xb6, + 0xba,0xbe,0xb9,0xba,0xb9,0xbb,0xba,0xba,0xba,0xb9,0xbc,0xbb,0xbc,0xbb,0xbc,0xc1, + 0x55,0x28,0x14,0x21,0x2a,0x2e,0x28,0x22,0x1e,0x1b,0x16,0x14,0x16,0x17,0x1a,0x1e, + 0x27,0x2f,0x36,0x3e,0x3e,0x3c,0x39,0x34,0x30,0x2a,0x27,0x24,0x20,0x1d,0x1a,0x19, + 0x70,0x6d,0x69,0x62,0x66,0x6b,0x71,0x71,0x72,0x71,0x71,0x73,0x7b,0x83,0x86,0x7f, + 0x76,0x6d,0x6b,0x70,0x70,0x64,0x4d,0x3b,0x38,0x43,0x50,0x5b,0x64,0x69,0x6c,0x6e, + 0x72,0x77,0x7e,0x84,0x88,0x8c,0x8e,0x91,0x92,0x94,0x95,0x94,0x91,0x8d,0x87,0x81, + 0x7e,0x7e,0x80,0x80,0x81,0x84,0x90,0x9a,0x9e,0xa6,0xb0,0xb0,0xb2,0xb7,0xb9,0x44, + 0x63,0x70,0x26,0x1e,0x20,0x20,0x21,0x22,0x25,0x22,0x22,0x24,0x25,0x26,0x26,0x2b, + 0x2f,0x32,0x3a,0x3e,0x46,0x4c,0x53,0x58,0x5f,0x65,0x6c,0x74,0x7c,0x86,0x8f,0x9b, + 0x43,0x24,0x17,0x29,0x2e,0x2b,0x29,0x21,0x1d,0x19,0x14,0x16,0x16,0x18,0x1a,0x21, + 0x28,0x31,0x39,0x3f,0x3f,0x3f,0x3b,0x39,0x36,0x32,0x2a,0x29,0x25,0x1e,0x1c,0x1b, + 0x6d,0x6d,0x65,0x62,0x68,0x69,0x70,0x71,0x71,0x6e,0x71,0x73,0x78,0x7c,0x80,0x7a, + 0x73,0x6d,0x6a,0x6f,0x71,0x63,0x4d,0x3c,0x3c,0x44,0x50,0x5b,0x64,0x69,0x6c,0x6e, + 0x70,0x76,0x7c,0x81,0x85,0x89,0x8d,0x90,0x91,0x91,0x91,0x8e,0x8a,0x88,0x81,0x7c, + 0x7a,0x7a,0x7c,0x80,0x84,0x84,0x83,0x8b,0x92,0x9a,0xa5,0xa8,0xa9,0xb5,0xba,0x9f, + 0x46,0x8b,0xa0,0x8d,0x81,0x77,0x6d,0x5a,0x4a,0x47,0x3f,0x36,0x30,0x2c,0x25,0x25, + 0x26,0x29,0x27,0x29,0x29,0x29,0x28,0x29,0x2b,0x2e,0x2a,0x2c,0x2d,0x28,0x28,0x29, + 0x26,0x1d,0x70,0x5e,0x2d,0x2c,0x27,0x21,0x1f,0x1a,0x15,0x16,0x17,0x18,0x1c,0x24, + 0x2b,0x33,0x3b,0x3d,0x3d,0x3e,0x3d,0x3c,0x38,0x33,0x2e,0x29,0x24,0x20,0x1d,0x1c, + 0x6f,0x6c,0x67,0x65,0x62,0x66,0x6d,0x6f,0x72,0x74,0x74,0x74,0x74,0x72,0x77,0x77, + 0x71,0x6e,0x6a,0x6e,0x6e,0x61,0x4c,0x3b,0x3d,0x44,0x52,0x5f,0x64,0x69,0x6c,0x6e, + 0x71,0x74,0x79,0x7d,0x81,0x85,0x86,0x89,0x88,0x88,0x85,0x84,0x81,0x7e,0x7a,0x78, + 0x77,0x77,0x7b,0x7d,0x7e,0x80,0x82,0x84,0x87,0x88,0x8e,0x97,0x9c,0xa7,0xb3,0xb1, + 0x8d,0x8e,0xb8,0xb8,0xd5,0xd5,0xdb,0xc8,0xc5,0xc6,0xc2,0xbb,0xb6,0xb6,0xb5,0xb1, + 0xae,0xae,0xa5,0x9c,0x91,0x88,0x7d,0x75,0x6c,0x62,0x55,0x48,0x3d,0x37,0x33,0x26, + 0x26,0x6e,0xcb,0x2b,0x2f,0x2b,0x25,0x22,0x22,0x1e,0x19,0x17,0x17,0x19,0x1e,0x23, + 0x29,0x2e,0x33,0x36,0x37,0x39,0x3a,0x3a,0x37,0x35,0x34,0x2d,0x2a,0x26,0x22,0x20, + 0x6f,0x6d,0x6b,0x6b,0x65,0x64,0x69,0x6c,0x76,0x79,0x77,0x75,0x73,0x6d,0x6f,0x74, + 0x72,0x6f,0x6c,0x6a,0x6a,0x5d,0x49,0x3c,0x3e,0x45,0x54,0x5e,0x64,0x69,0x6c,0x6e, + 0x70,0x73,0x77,0x79,0x7d,0x7e,0x7d,0x7c,0x7b,0x7c,0x7a,0x7a,0x79,0x77,0x73,0x73, + 0x73,0x78,0x7d,0x81,0x82,0x7f,0x7e,0x7e,0x7d,0x80,0x81,0x80,0x80,0x8c,0x9d,0xaa, + 0xa8,0xa7,0xa9,0xb7,0xc0,0xc6,0xcd,0xd4,0xd9,0xd8,0xdb,0xda,0xd3,0xd6,0xd5,0xd1, + 0xd4,0xd4,0xcf,0xd3,0xd6,0xd3,0xcf,0xce,0xcb,0xd1,0xd4,0xd2,0xcd,0xcc,0xd2,0xca, + 0xdd,0xf5,0xa1,0x2d,0x30,0x30,0x2d,0x2a,0x28,0x23,0x1c,0x18,0x17,0x18,0x1d,0x22, + 0x24,0x25,0x27,0x2b,0x2d,0x2f,0x33,0x33,0x35,0x36,0x38,0x35,0x31,0x2e,0x29,0x23, + 0x6d,0x71,0x73,0x71,0x6c,0x69,0x6a,0x6e,0x73,0x76,0x76,0x77,0x77,0x71,0x72,0x74, + 0x70,0x70,0x6b,0x6b,0x6a,0x5a,0x46,0x3b,0x3a,0x47,0x56,0x5e,0x66,0x6c,0x6d,0x6e, + 0x70,0x70,0x73,0x74,0x75,0x75,0x72,0x6f,0x6d,0x6f,0x70,0x71,0x71,0x71,0x71,0x6f, + 0x71,0x74,0x7e,0x84,0x87,0x86,0x82,0x7f,0x7a,0x78,0x79,0x7a,0x77,0x76,0x88,0x9b, + 0xa3,0xa3,0xa4,0xa5,0xab,0xba,0xcc,0xd8,0xdd,0xdd,0xdf,0xde,0xd4,0xc7,0xc4,0xce, + 0xca,0xb8,0xa5,0x80,0x58,0x64,0x76,0x88,0xa2,0xb5,0xc7,0xd7,0xdd,0xde,0xe4,0xea, + 0xf3,0xa4,0x38,0x33,0x34,0x37,0x34,0x30,0x2b,0x26,0x1e,0x18,0x17,0x16,0x1a,0x1e, + 0x21,0x21,0x21,0x22,0x25,0x25,0x2b,0x2e,0x30,0x34,0x36,0x35,0x32,0x2f,0x29,0x25, + 0x6b,0x70,0x72,0x72,0x6e,0x6c,0x6b,0x6f,0x73,0x73,0x73,0x76,0x76,0x77,0x78,0x74, + 0x72,0x70,0x6b,0x68,0x66,0x56,0x40,0x39,0x36,0x47,0x59,0x62,0x67,0x6c,0x6c,0x6e, + 0x6f,0x6e,0x6f,0x70,0x6f,0x6d,0x69,0x64,0x62,0x63,0x68,0x6f,0x6e,0x6f,0x6c,0x6c, + 0x6f,0x75,0x7d,0x83,0x87,0x88,0x86,0x82,0x80,0x7d,0x7a,0x78,0x77,0x77,0x7c,0x8c, + 0x98,0xa2,0xa7,0xb6,0xc6,0xd6,0xdd,0xde,0xdc,0xd8,0xd4,0xca,0xbd,0xbc,0xbd,0xc9, + 0xbe,0xaf,0x90,0x55,0x1e,0x21,0x20,0x1f,0x1a,0x19,0x19,0x1e,0x27,0x34,0x44,0x4a, + 0x38,0x32,0x3a,0x37,0x38,0x3b,0x38,0x33,0x2f,0x27,0x1d,0x18,0x15,0x15,0x1a,0x1a, + 0x1b,0x1b,0x1c,0x1b,0x1d,0x23,0x25,0x2b,0x2c,0x32,0x35,0x35,0x33,0x2f,0x2c,0x27, + 0x65,0x6b,0x6f,0x70,0x6c,0x6c,0x6a,0x6d,0x6c,0x6b,0x69,0x6c,0x76,0x7a,0x7d,0x76, + 0x73,0x6c,0x65,0x66,0x62,0x50,0x3d,0x35,0x37,0x4a,0x5b,0x64,0x6b,0x6c,0x6b,0x6c, + 0x6d,0x6b,0x6b,0x6b,0x69,0x66,0x62,0x5b,0x59,0x5c,0x63,0x6b,0x68,0x68,0x6a,0x6a, + 0x6f,0x76,0x7c,0x82,0x86,0x87,0x86,0x82,0x80,0x80,0x7f,0x7d,0x78,0x77,0x82,0x8b, + 0x90,0x9f,0xb4,0xc4,0xd1,0xda,0xd8,0xd3,0xcc,0xbe,0xae,0xa4,0xaa,0xb1,0xb3,0xc0, + 0xb7,0xa3,0x80,0x38,0x1f,0x22,0x24,0x25,0x29,0x2e,0x2e,0x31,0x34,0x35,0x39,0x3a, + 0x3a,0x3a,0x38,0x39,0x39,0x3a,0x39,0x36,0x32,0x29,0x20,0x1c,0x16,0x13,0x16,0x17, + 0x17,0x18,0x18,0x18,0x1a,0x1d,0x1f,0x23,0x26,0x2f,0x30,0x31,0x31,0x2d,0x28,0x28, + 0x64,0x69,0x6d,0x6c,0x68,0x64,0x67,0x69,0x68,0x67,0x66,0x68,0x72,0x79,0x7e,0x7b, + 0x71,0x6c,0x65,0x64,0x61,0x4d,0x38,0x31,0x39,0x4d,0x5e,0x67,0x6c,0x6c,0x6c,0x6b, + 0x6a,0x69,0x68,0x68,0x67,0x62,0x5c,0x56,0x52,0x53,0x5c,0x60,0x66,0x6e,0x6c,0x6e, + 0x73,0x7a,0x7e,0x82,0x86,0x87,0x86,0x82,0x82,0x82,0x82,0x82,0x82,0x80,0x86,0x93, + 0x95,0xa3,0xb5,0xc1,0xc6,0xcc,0xc6,0xb7,0x9a,0x7c,0x7a,0x8c,0x9a,0xa1,0xb4,0xb9, + 0xb1,0x9a,0x6e,0x2c,0x20,0x23,0x24,0x29,0x2a,0x2e,0x33,0x36,0x38,0x38,0x39,0x3c, + 0x38,0x36,0x39,0x3b,0x3b,0x3b,0x3a,0x3b,0x36,0x2d,0x27,0x24,0x1d,0x18,0x15,0x14, + 0x15,0x16,0x17,0x18,0x19,0x18,0x18,0x1b,0x20,0x28,0x28,0x28,0x2a,0x29,0x29,0x2c, + 0x67,0x69,0x69,0x69,0x69,0x63,0x63,0x63,0x62,0x60,0x5f,0x63,0x6c,0x76,0x79,0x7a, + 0x70,0x6a,0x68,0x68,0x60,0x4e,0x38,0x32,0x3b,0x50,0x60,0x68,0x6b,0x6a,0x69,0x68, + 0x67,0x67,0x67,0x66,0x64,0x60,0x5b,0x56,0x49,0x44,0x4e,0x58,0x65,0x71,0x70,0x74, + 0x78,0x7c,0x7f,0x83,0x86,0x86,0x85,0x83,0x82,0x81,0x80,0x82,0x84,0x86,0x89,0x94, + 0x99,0x9b,0xa6,0xac,0xaf,0xa6,0x92,0x72,0x50,0x48,0x5a,0x6e,0x7f,0x8f,0xaf,0xb7, + 0xab,0x93,0x61,0x24,0x23,0x24,0x25,0x2a,0x2c,0x2d,0x2d,0x30,0x32,0x35,0x38,0x3a, + 0x37,0x37,0x37,0x38,0x3a,0x3c,0x3c,0x3a,0x37,0x32,0x2e,0x29,0x26,0x1c,0x16,0x15, + 0x13,0x13,0x13,0x15,0x15,0x15,0x17,0x18,0x1b,0x24,0x27,0x26,0x28,0x2b,0x2c,0x32, + 0x6f,0x69,0x65,0x67,0x68,0x62,0x5e,0x5e,0x5f,0x60,0x60,0x61,0x6a,0x74,0x77,0x79, + 0x72,0x6e,0x6a,0x69,0x64,0x50,0x39,0x35,0x3f,0x54,0x63,0x69,0x6a,0x67,0x66,0x65, + 0x64,0x66,0x66,0x66,0x65,0x61,0x59,0x53,0x48,0x41,0x4b,0x58,0x66,0x73,0x78,0x7a, + 0x7c,0x7c,0x7e,0x81,0x84,0x85,0x85,0x83,0x81,0x81,0x80,0x82,0x84,0x87,0x8d,0x94, + 0x99,0x9c,0x95,0x8e,0x81,0x68,0x51,0x42,0x44,0x44,0x47,0x54,0x66,0x80,0xa3,0xa7, + 0x9c,0x86,0x53,0x20,0x23,0x23,0x24,0x25,0x27,0x28,0x28,0x2a,0x2d,0x2e,0x30,0x32, + 0x33,0x34,0x34,0x38,0x3c,0x3c,0x3b,0x3b,0x3e,0x3a,0x38,0x33,0x2c,0x20,0x18,0x15, + 0x12,0x11,0x11,0x12,0x12,0x12,0x15,0x18,0x1e,0x24,0x27,0x2b,0x2e,0x30,0x34,0x3a, + 0x70,0x6a,0x64,0x66,0x65,0x62,0x5f,0x58,0x5e,0x64,0x64,0x67,0x6b,0x72,0x75,0x78, + 0x73,0x70,0x6a,0x69,0x61,0x4d,0x3c,0x37,0x43,0x56,0x66,0x67,0x64,0x64,0x65,0x65, + 0x65,0x66,0x64,0x65,0x63,0x5e,0x57,0x54,0x52,0x4c,0x55,0x60,0x6c,0x74,0x7c,0x7b, + 0x7a,0x7b,0x7c,0x7f,0x84,0x84,0x86,0x81,0x80,0x7e,0x7d,0x82,0x82,0x84,0x8c,0x93, + 0x96,0x9b,0x92,0x81,0x6c,0x53,0x45,0x43,0x42,0x42,0x42,0x45,0x4f,0x67,0x8b,0x90, + 0x83,0x6b,0x3c,0x22,0x1f,0x21,0x21,0x20,0x1f,0x21,0x23,0x25,0x25,0x28,0x2a,0x31, + 0x32,0x33,0x36,0x39,0x3b,0x3a,0x3a,0x39,0x3b,0x3a,0x39,0x36,0x2d,0x22,0x19,0x13, + 0x11,0x0e,0x0f,0x10,0x10,0x11,0x15,0x1b,0x26,0x27,0x2a,0x2e,0x34,0x35,0x38,0x3d, + 0x6e,0x68,0x67,0x6a,0x68,0x66,0x60,0x5b,0x5f,0x6c,0x6d,0x72,0x71,0x74,0x76,0x79, + 0x74,0x70,0x68,0x65,0x5f,0x4b,0x3e,0x3d,0x47,0x59,0x64,0x61,0x61,0x63,0x63,0x64, + 0x65,0x65,0x64,0x65,0x61,0x59,0x59,0x56,0x57,0x5b,0x61,0x6c,0x75,0x7a,0x7e,0x7d, + 0x7c,0x7a,0x7b,0x7e,0x82,0x84,0x84,0x81,0x7f,0x7e,0x7d,0x7f,0x82,0x84,0x8a,0x92, + 0x96,0x99,0x92,0x7f,0x69,0x53,0x46,0x41,0x40,0x3f,0x40,0x41,0x48,0x55,0x6d,0x70, + 0x66,0x4b,0x27,0x1e,0x1f,0x1e,0x1f,0x1e,0x1d,0x1f,0x20,0x1f,0x21,0x23,0x2a,0x2b, + 0x2f,0x35,0x37,0x3a,0x3a,0x39,0x36,0x36,0x35,0x3a,0x37,0x33,0x2a,0x22,0x17,0x10, + 0x0d,0x0d,0x0e,0x0f,0x11,0x12,0x17,0x1e,0x26,0x28,0x2b,0x31,0x36,0x39,0x3c,0x3d, +}; diff --git a/partitions_example.csv b/partitions_example.csv new file mode 100644 index 0000000..c02ec11 --- /dev/null +++ b/partitions_example.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, spiffs, 0x180000, 1M, diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..3a6598f --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,10 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" +CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 +CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" +CONFIG_APP_OFFSET=0x10000 +CONFIG_ESP32_PANIC_PRINT_HALT=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_MAIN_TASK_STACK_SIZE=8192 +CONFIG_TASK_WDT_TIMEOUT_S=20