From 49f765737e28131b8704cc0b4ce2d96a743dcd0b Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Tue, 26 Nov 2019 11:32:40 +0530 Subject: [PATCH] initial implementation of registration captcha --- .../org/ros/chatto/ChattoApplication.java | 18 +- .../ros/chatto/WebSecurityConfiguration.java | 3 +- .../ros/chatto/captcha/CaptchaBehaviour.java | 10 + .../captcha/ManualCaptchaBehaviour.java | 37 ++++ .../org/ros/chatto/captcha/SimpleCaptcha.java | 183 ++++++++++++++++++ .../chatto/captcha/SimpleCaptchaBehavior.java | 40 ++++ .../org/ros/chatto/captcha/WebCaptcha.java | 26 +++ .../ros/chatto/captcha/WebCaptchaBuilder.java | 16 ++ .../controller/RegistrationController.java | 58 +++++- .../ros/chatto/dto/UserRegistrationDTO.java | 25 +-- .../ros/chatto/service/CaptchaService.java | 26 +++ chatto/src/main/resources/pictures/A.png | Bin 0 -> 612 bytes chatto/src/main/resources/pictures/B.png | Bin 0 -> 603 bytes chatto/src/main/resources/pictures/C.png | Bin 0 -> 590 bytes chatto/src/main/resources/pictures/D.png | Bin 0 -> 561 bytes chatto/src/main/resources/pictures/E.png | Bin 0 -> 446 bytes chatto/src/main/resources/pictures/F.png | Bin 0 -> 391 bytes chatto/src/main/resources/pictures/G.png | Bin 0 -> 694 bytes chatto/src/main/resources/pictures/H.png | Bin 0 -> 466 bytes chatto/src/main/resources/pictures/I.png | Bin 0 -> 381 bytes chatto/src/main/resources/pictures/J.png | Bin 0 -> 439 bytes chatto/src/main/resources/pictures/K.png | Bin 0 -> 582 bytes chatto/src/main/resources/pictures/L.png | Bin 0 -> 387 bytes chatto/src/main/resources/pictures/M.png | Bin 0 -> 911 bytes chatto/src/main/resources/pictures/N.png | Bin 0 -> 606 bytes chatto/src/main/resources/pictures/O.png | Bin 0 -> 732 bytes chatto/src/main/resources/pictures/P.png | Bin 0 -> 588 bytes chatto/src/main/resources/pictures/Q.png | Bin 0 -> 830 bytes chatto/src/main/resources/pictures/R.png | Bin 0 -> 693 bytes chatto/src/main/resources/pictures/S.png | Bin 0 -> 775 bytes chatto/src/main/resources/pictures/T.png | Bin 0 -> 378 bytes chatto/src/main/resources/pictures/U.png | Bin 0 -> 656 bytes chatto/src/main/resources/pictures/V.png | Bin 0 -> 705 bytes chatto/src/main/resources/pictures/W.png | Bin 0 -> 755 bytes chatto/src/main/resources/pictures/X.png | Bin 0 -> 715 bytes chatto/src/main/resources/pictures/Y.png | Bin 0 -> 655 bytes chatto/src/main/resources/pictures/Z.png | Bin 0 -> 568 bytes .../resources/templates/registration.html | 9 + 38 files changed, 427 insertions(+), 24 deletions(-) create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java create mode 100644 chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java create mode 100644 chatto/src/main/java/org/ros/chatto/service/CaptchaService.java create mode 100644 chatto/src/main/resources/pictures/A.png create mode 100644 chatto/src/main/resources/pictures/B.png create mode 100644 chatto/src/main/resources/pictures/C.png create mode 100644 chatto/src/main/resources/pictures/D.png create mode 100644 chatto/src/main/resources/pictures/E.png create mode 100644 chatto/src/main/resources/pictures/F.png create mode 100644 chatto/src/main/resources/pictures/G.png create mode 100644 chatto/src/main/resources/pictures/H.png create mode 100644 chatto/src/main/resources/pictures/I.png create mode 100644 chatto/src/main/resources/pictures/J.png create mode 100644 chatto/src/main/resources/pictures/K.png create mode 100644 chatto/src/main/resources/pictures/L.png create mode 100644 chatto/src/main/resources/pictures/M.png create mode 100644 chatto/src/main/resources/pictures/N.png create mode 100644 chatto/src/main/resources/pictures/O.png create mode 100644 chatto/src/main/resources/pictures/P.png create mode 100644 chatto/src/main/resources/pictures/Q.png create mode 100644 chatto/src/main/resources/pictures/R.png create mode 100644 chatto/src/main/resources/pictures/S.png create mode 100644 chatto/src/main/resources/pictures/T.png create mode 100644 chatto/src/main/resources/pictures/U.png create mode 100644 chatto/src/main/resources/pictures/V.png create mode 100644 chatto/src/main/resources/pictures/W.png create mode 100644 chatto/src/main/resources/pictures/X.png create mode 100644 chatto/src/main/resources/pictures/Y.png create mode 100644 chatto/src/main/resources/pictures/Z.png diff --git a/chatto/src/main/java/org/ros/chatto/ChattoApplication.java b/chatto/src/main/java/org/ros/chatto/ChattoApplication.java index c2f4909..11ab5b0 100644 --- a/chatto/src/main/java/org/ros/chatto/ChattoApplication.java +++ b/chatto/src/main/java/org/ros/chatto/ChattoApplication.java @@ -2,13 +2,10 @@ package org.ros.chatto; import java.sql.SQLException; -import org.ros.chatto.service.DBInitializerService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication public class ChattoApplication extends SpringBootServletInitializer { @@ -16,6 +13,21 @@ public class ChattoApplication extends SpringBootServletInitializer { public static void main(String[] args) throws SQLException { SpringApplication application = new SpringApplication(ChattoApplication.class); application.run(); + +// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build(); +// webCaptcha.generateCaptcha(); +// +// // @formatter:off +// webCaptcha = WebCaptcha.builder() +// .captchaBehaviour( +// ManualCaptchaBehaviour.builder() +// .length(8) +// .style("black") +// .build() +// ).build(); +// +// // @formatter:on + } @Override diff --git a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java index 3455340..7309125 100644 --- a/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java +++ b/chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java @@ -77,7 +77,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { .anyRequest() // .hasAnyRole("USER", "ADMIN", "SUPER_USER") - .authenticated().and().httpBasic().authenticationEntryPoint(authenticationEntryPoint) + .authenticated() + .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint) // .and() // .logout().invalidateHttpSession(true).clearAuthentication(true) // .logoutRequestMatcher(new AntPathRequestMatcher("/api/perform_logout")) diff --git a/chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java b/chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java new file mode 100644 index 0000000..ead3ba2 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java @@ -0,0 +1,10 @@ +package org.ros.chatto.captcha; + +import java.awt.image.BufferedImage; + +interface CaptchaBehaviour { + public BufferedImage generateCaptcha(); + public BufferedImage generateCaptcha(String captchaText); + public String getRandomChars(int size); + public String getRandomChars(); +} diff --git a/chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java b/chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java new file mode 100644 index 0000000..a33ff07 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java @@ -0,0 +1,37 @@ +package org.ros.chatto.captcha; + +import java.awt.image.BufferedImage; + +import lombok.Builder; + +/*Class for providing your own captcha generator*/ +@Builder +public class ManualCaptchaBehaviour implements CaptchaBehaviour{ + private final int length; + private final String style; + @Override + public BufferedImage generateCaptcha() { + // TODO Auto-generated method stub + return null; + } + + @Override + public BufferedImage generateCaptcha(String captchaText) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRandomChars(int size) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getRandomChars() { + // TODO Auto-generated method stub + return null; + } + + +} diff --git a/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java b/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java new file mode 100644 index 0000000..7849576 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java @@ -0,0 +1,183 @@ +package org.ros.chatto.captcha; + +import javax.imageio.ImageIO; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Random; + +/** + * This class represents a simple captcha consisting + * of an image {@code png} and its text value. + * Comic Neue Bold Font. + * Capital english letters {@code ONLY}. + * + * @since 1.3 + * @author Gennadiy Golovin + */ +public final class SimpleCaptcha { + + private BufferedImage imagePng; + private char[] text; + + /** + * Initializes a newly created default object + * consisting of 8 capital english letters. + */ + public SimpleCaptcha() { + this.text = getRandomChars(); + + try { + generateCaptcha(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Initializes a newly created object, which length + * depends on the passed {@code int} parameter, + * which {@code MUST} be greater than 0. + * If the condition is not met, initializes a newly + * created default object consisting of 8 symbols. + * + * @param length the quantity of symbols, that the + * captcha consists of, greater than 0. + */ + public SimpleCaptcha(int length) { + if (length < 1) { + this.text = getRandomChars(); + } else { + this.text = getRandomChars(length); + } + + try { + generateCaptcha(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Initializes a newly created object based on the passed + * {@link String} parameter, consisting of capital english + * letters. If the condition is not met, initializes a newly + * created default object consisting of 8 capital english letters. + * + * @param text the text string with the value of the captcha, + * length greater than 0. + */ + public SimpleCaptcha(String text) { + if (text == null || text.equals("")) { + this.text = getRandomChars(); + } else { + this.text = text.toCharArray(); + } + + try { + generateCaptcha(); + } catch (IOException e) { + this.text = getRandomChars(); + try { + generateCaptcha(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + /** + * Returns the picture with captcha + * + * @return {@link BufferedImage} + */ + public BufferedImage getImagePng() { + return imagePng; + } + + /** + * Returns the text value of the captcha + * + * @return {@link String} + */ + public String getText() { + return String.valueOf(text); + } + + //////// //////// //////// //////// //////// //////// //////// //////// + + private char[] getRandomChars() { + return getRandomChars(8); + } + + private char[] getRandomChars(int quantity) { + + char[] randomString = new char[quantity]; + + Random random = new Random(); + + int capitalLetter; + + for (int i = 0; i < quantity; i++) { + capitalLetter = 65 + random.nextInt(26); + randomString[i] = (char) capitalLetter; + } + + return randomString; + } + + private void generateCaptcha() throws IOException { + int charsQuantity = this.text.length; + BufferedImage[] images = new BufferedImage[charsQuantity]; + + for (int i = 0; i < charsQuantity; i++) { + images[i] = ImageIO.read(SimpleCaptcha.class.getResourceAsStream("/pictures/" + this.text[i] + ".png")); + if (i % 2 == 0) { + images[i] = rotateImage(images[i], 25); + } else { + images[i] = rotateImage(images[i], -20); + } + } + + int imageSize = 30; + int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2); + + BufferedImage captchaImg = new BufferedImage(rotatedImageSize * (charsQuantity - 1) / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics2d = captchaImg.createGraphics(); + graphics2d.setBackground(Color.WHITE); + graphics2d.clearRect(0, 0, captchaImg.getWidth(), captchaImg.getHeight()); + for (int i = 0; i < charsQuantity; i++) { + captchaImg.getGraphics().drawImage(images[i], rotatedImageSize * i / 10 * 6, 0, null); + } + graphics2d.dispose(); + this.imagePng = captchaImg; + } + + private BufferedImage rotateImage(BufferedImage buffImage, double angle) { + + double radian = Math.toRadians(angle); + double sin = Math.abs(Math.sin(radian)); + double cos = Math.abs(Math.cos(radian)); + + int width = buffImage.getWidth(); + int height = buffImage.getHeight(); + + int nWidth = (int) Math.floor((double) width * cos + (double) height * sin); + int nHeight = (int) Math.floor((double) height * cos + (double) width * sin); + + BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB); + + Graphics2D graphics = rotatedImage.createGraphics(); + + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + graphics.translate((nWidth - width) / 2, (nHeight - height) / 2); + graphics.rotate(radian, (double) (width / 2), (double) (height / 2)); + graphics.drawImage(buffImage, 0, 0,null); + graphics.dispose(); + + return rotatedImage; + } +} \ No newline at end of file diff --git a/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java b/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java new file mode 100644 index 0000000..f317421 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java @@ -0,0 +1,40 @@ +package org.ros.chatto.captcha; + +import java.awt.image.BufferedImage; +import java.util.Random; + +public class SimpleCaptchaBehavior implements CaptchaBehaviour { + @Override + public BufferedImage generateCaptcha() { + // TODO Auto-generated method stub + SimpleCaptcha simpleCaptcha = new SimpleCaptcha(); + return simpleCaptcha.getImagePng(); + } + @Override + public BufferedImage generateCaptcha(String captchaText) { + // TODO Auto-generated method stub + SimpleCaptcha simpleCaptcha = new SimpleCaptcha(captchaText); + return simpleCaptcha.getImagePng(); + } + + public String getRandomChars() { + return getRandomChars(8); + } + + public String getRandomChars(int quantity) + { + char[] randomString = new char[quantity]; + + Random random = new Random(); + + int capitalLetter; + + for (int i = 0; i < quantity; i++) { + capitalLetter = 65 + random.nextInt(26); + randomString[i] = (char) capitalLetter; + } + + return new String(randomString); + } + +} diff --git a/chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java b/chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java new file mode 100644 index 0000000..1218279 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java @@ -0,0 +1,26 @@ +package org.ros.chatto.captcha; + +import java.awt.image.BufferedImage; + +import lombok.Builder; + +@Builder +public class WebCaptcha { + private final CaptchaBehaviour captchaBehaviour; + + public BufferedImage generateCaptcha() { + return captchaBehaviour.generateCaptcha(); + } + + public BufferedImage generateCaptcha(String captchaText) { + return captchaBehaviour.generateCaptcha(captchaText); + } + + public String getRandomChars() { + return captchaBehaviour.getRandomChars(); + } + + public String getRandomChars(int quantity) { + return captchaBehaviour.getRandomChars(quantity); + } +} diff --git a/chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java b/chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java new file mode 100644 index 0000000..e7c7d24 --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java @@ -0,0 +1,16 @@ +package org.ros.chatto.captcha; + +import lombok.Builder; + +@Builder +public class WebCaptchaBuilder { + private CaptchaBehaviour captchaBehaviour; +// public WebCaptchaBuilder(CaptchaBehaviour captchaBehaviour) +// { +// this.captchaBehaviour = captchaBehaviour; +// } + public WebCaptcha build() + { + return new WebCaptcha(captchaBehaviour); + } +} diff --git a/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java b/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java index 02ed450..367d580 100644 --- a/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java +++ b/chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java @@ -1,15 +1,31 @@ package org.ros.chatto.controller; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +import javax.imageio.ImageIO; import javax.validation.Valid; import org.ros.chatto.dto.UserRegistrationDTO; +import org.ros.chatto.service.CaptchaService; import org.ros.chatto.service.UserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @Controller @@ -18,9 +34,23 @@ public class RegistrationController { @Autowired private UserService userService; + @Autowired + private CaptchaService captchaService; + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Map captchaMap = new ConcurrentHashMap<>(); + @GetMapping("/registration") public String registrationForm(Model model) { - model.addAttribute("userRegistrationDTO", new UserRegistrationDTO()); + UserRegistrationDTO userRegistrationDTO = new UserRegistrationDTO(); + String captchaText = captchaService.getRandomText(); + userRegistrationDTO.setCaptchaText(captchaText); + logger.debug("captcha text = {}", captchaText); + Long captchaID = ThreadLocalRandom.current().nextLong(); + userRegistrationDTO.setCaptchaID(captchaID); + captchaMap.put(captchaID, captchaText); + model.addAttribute("userRegistrationDTO", userRegistrationDTO); return "registration"; } @@ -29,10 +59,32 @@ public class RegistrationController { @ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO, BindingResult bindingResult) { if (bindingResult.hasErrors()) { - System.out.println("Input has errors!"); + logger.warn("Registration input has errors!"); return "registration"; } - userService.registerUser(userRegistrationDTO); + logger.debug("Captcha text from user input = {}", userRegistrationDTO.getCaptchaInput()); + logger.debug("Captcha text from captcha map = {}", captchaMap.get(userRegistrationDTO.getCaptchaID())); + if (userRegistrationDTO.getCaptchaInput().equals(captchaMap.get(userRegistrationDTO.getCaptchaID()))) { + logger.info("Registration captcha equal success"); + } else { + logger.warn("Registration captcha equal fail"); + } +// userService.registerUser(userRegistrationDTO); return "user/home"; } + + @GetMapping(value = "/img/{image_id}", produces = MediaType.IMAGE_PNG_VALUE) + public ResponseEntity getImage(@PathVariable("image_id") Long imageId) throws IOException { + + final String captchaText = captchaMap.get(imageId); + final HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_PNG); + BufferedImage captchaBufferedImage = captchaService.createCaptchaImage(captchaText); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(captchaBufferedImage, "png", baos); + byte[] imageBytes = baos.toByteArray(); + + return new ResponseEntity(imageBytes, headers, HttpStatus.OK); + } } diff --git a/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java b/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java index f049238..b47a339 100644 --- a/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java +++ b/chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java @@ -5,6 +5,9 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; +import lombok.Data; + +@Data public class UserRegistrationDTO { @Size(min = 4, max = 10, message = "Username must be between 4 and 10 characters") @NotBlank(message = "Username should not be blank") @@ -15,21 +18,9 @@ public class UserRegistrationDTO { @NotBlank(message = "Password should not be blank") // @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format") private String password; - - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - + + private Long captchaID; + private String captchaText; + + private String captchaInput; } diff --git a/chatto/src/main/java/org/ros/chatto/service/CaptchaService.java b/chatto/src/main/java/org/ros/chatto/service/CaptchaService.java new file mode 100644 index 0000000..31bfa7d --- /dev/null +++ b/chatto/src/main/java/org/ros/chatto/service/CaptchaService.java @@ -0,0 +1,26 @@ +package org.ros.chatto.service; + +import java.awt.image.BufferedImage; + +import org.ros.chatto.captcha.SimpleCaptchaBehavior; +import org.ros.chatto.captcha.WebCaptcha; +import org.springframework.stereotype.Service; + +@Service +public class CaptchaService { + private final WebCaptcha webCaptcha; + + public CaptchaService() { + webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build(); + } + + public BufferedImage createCaptchaImage(String captchaText) + { + return webCaptcha.generateCaptcha(captchaText); + } + + public String getRandomText() + { + return webCaptcha.getRandomChars(); + } +} diff --git a/chatto/src/main/resources/pictures/A.png b/chatto/src/main/resources/pictures/A.png new file mode 100644 index 0000000000000000000000000000000000000000..bde14697edf53d5d65fb44046910e88fd0a62b1d GIT binary patch literal 612 zcmV-q0-ODbP)0R+O+@x03B&mSad^g zZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00GQNL_t(I%f-~qOO#O*$MKJo zW@;rFX(>oAhNzG)5aBQdgCPWh6#WDJ2|?=?wQcVvqJnloM7xlp7o^f;3WU(aT7@N8 z4fdjqw}rzP&ht#pBKqCUeK_~~oOA9y=kO1ekcc;KDT(fON|#T*mG8B}6{~`awwDk0 zc_|29X^YD#UTKHH5gpO##lww~cI5Yi>dFgx*qo4GGY*yGxk}3Z!dIJ~sKDdkPD$`y z77VG~JlwAvTr)0UP}64NCMk88cGsvSG&@!tcr@IT4|>#F56pwpwrvKk*6#qmdSuC@ zX#r_1D*g_)NrR`JVa1pwG)QlRDM8{`yX>*W!tssk!p6YYRqMQ8MZg!&UAG_z#?-{+oX}8s%bX8hn~*VU!Dsb`RO-`V zGOqc1J<0nU(W+ie;b-S$1;M3KV`oGV%o#4ajafmEiK_86b?1-V)>ve_Wks+o6K!%q<6CJ6?fN~b~>)TL`VR&G literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/B.png b/chatto/src/main/resources/pictures/B.png new file mode 100644 index 0000000000000000000000000000000000000000..4404eec5091d059674db5190c939b7a58bd0c0d8 GIT binary patch literal 603 zcmV-h0;K(kP)WFU8GbZ8()Nlj2>E@cM*00F~EL_t(I%gxk1XcTb}1@K=k zcM{D9iK1Ra#Sa9Dr4mI!j93I4!7kWYS(rv5c48}}OlO~7EEEMHooFM(C>91KArVod zo{74f+rNdIGk3Y$ODdhI-oV>=|CzVLzlxR`oU=vys%o$-5{V^i$@v<5&@BUkkmAG- zv!1wb(${Pq1y_W+8N1`q8l6?K*>ypXSgvGJ`M_iQvShEw{pOi@t!UAq*8y8GV8k_1 zqt0zc1i_?}tExp;O-lqNhpVSse+QdZq4&m&Swe?X>mk>U=AlKfr#X3T>{o#5b=4;) zepTU}F6#u(vrO_Qx}4FK_M*q2L3un~54qQ{B{>9i*#=^x7Mha_igm|$-b+)>_S`7T zCiFGyJZIdC^vyv*(Ftc2;6wG2>#EJ(INBT~kebcGqoh8k1x5VmTqbXJv`VC$Btm&)a54 pf4d?55|Kq;d@y6$J0<^F{03j#uY26M4Bh|$002ovPDHLkV1kE(0bu|D literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/C.png b/chatto/src/main/resources/pictures/C.png new file mode 100644 index 0000000000000000000000000000000000000000..613281579d3e8292870d80c2040c59cb94d0df2e GIT binary patch literal 590 zcmV-U0WFU8GbZ8()Nlj2>E@cM*00Fj1L_t(I%gxkJNEBfh$MKJw zwWj@p{t$}^DJdC-Xg4E>4uKsybV~5jrAwC%QP81IUAq#p;58EV(v(cM%128NgU&G&hqd7pV6{*_u%os6t~IuMBzl@S_)_c-IK^NwQ0 zf(cWyzWTN~K&^T_^h>C^*2HGywnK+ZBtWd7V51_$<;oXZYtR7?Tm;MB`zXli(<2MX zJw-WBlRamRYfqk@_Ds2U=W1o$5DS87XRDl(-pE^YDjgm)F9;T0s_{nMH=17Bc6S6p zujx4v<%9 z9u7*$54)pzcs)t?phL~W1#_UwP^0kQ6wBI-VZX==aotwEt=l^r^#XNyDF~KbujBTc zQxqAmpGB*&r16*Bw7be1@iy`9rhUX=kCKT|H0hc?M;vp(6W^0o{iHYj432yLXXL~p zKddHWAPBxVxAhTrdMw&B{DN57jN$Dou*(&bR^+WKDJfbt@0J6N4*i`5jW}Vig3n%? cvF1P0Z#rP4x@ae6`v3p{07*qoM6N<$g83Q#DF6Tf literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/D.png b/chatto/src/main/resources/pictures/D.png new file mode 100644 index 0000000000000000000000000000000000000000..d549359f0feaaddf9a64352b47527810260e954b GIT binary patch literal 561 zcmV-10?z%3P)WFU8GbZ8()Nlj2>E@cM*00EgvL_t(I%dOQrYm`A0#_`|Y zh{oK=8X`6+1T`wb6h;LRL_sVpy)=qg*rl)$`~vn8EH#q&4J<80ix?Clior@#2&)DR zt6|Ar-`B#`thw-JR-fq(=Rb3pbLJWTL8*0-G>FCF@J=JTl@xvR!-}f>4Tr&S%rgzK*reRrkx0_4+dhB` z4u`_bc%XtEE{DTRDS*>D!r_9~A`ICT4)aEYPP^NI3u2`8wFCctU)grxeksIC?Z5}5 zQ1c}m?s8lji#~T^d~Rb35+Ij>!MP|ku_QG` zp**uBL&4qCHy}kXm7RfsQO(oEF~s8Z)k%i_OpXGr`(p(cb*^1_(J!f#vz4{8_5bl7 z{(pqUj$Jx(?noQc)&?f$rYP?o$3>lnZaZ!-W_WV$cH(r0g~nIUp1D1{^8G=kg*I9z zJDy$??aEnrS-I1o1~o%{RN$9@|xkF$EBzi-vvve{_)`H`Nk m=S3Mt(ZE#K6Q;iZ9C#RASLJy9y!M;}6qKH>elF{r5}E+s9k$5; literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/F.png b/chatto/src/main/resources/pictures/F.png new file mode 100644 index 0000000000000000000000000000000000000000..d05909f87e5d52ec6f863d5f1afb4c764b23de8c GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX0wgC|rfC8xrX+877l!}s{b%+Ad7K3vk;M!Q z+`=Ht$S`Y;1W=H@#M9T6{V@}}umE>Oa@|s(kZfj1M2T~LZf;IDDkz!qDJ?b3>~_`qk?#5L$y5J()to7!(~QndJYIErpVOmaB7}QGv^r0p0#RE z+;dy^)aY)@;6D``+~+H93wnOUOEoCI^6aDHl`d&~zvVYPnfEMot|e346=s9~lInRO fGYlS`@;}b}cYl!m%o{SxKw;wP>gTe~DWM4fr1F#- literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/G.png b/chatto/src/main/resources/pictures/G.png new file mode 100644 index 0000000000000000000000000000000000000000..1356c3af4e345f83fc43b01e4f4b7b9c20ddaafa GIT binary patch literal 694 zcmV;n0!jUeP)WFU8GbZ8()Nlj2>E@cM*00JIKL_t(I%gxl?YmZ?V2k_6f z-`E(9nT1eFErc~I2V}{Sw-hO*oZtk10VO3bhfW+QQ<7RaATB9lLxrKUoEHQmu345>-S4X)cxO%_aW+LX zJ=9RJ`Pu07JM&93ToeSK?JYuPy|;pt?fIppdIdq-7=r9}&e04mVxD&jf*%f#Ddm(* zTP31*xh0W6kH-WLWYhGw$$aq5n~7hHMBHR5NcmY{*KBpFRS3T8@hKOaHW=BnG6%TT z4vS1vA%UcC-nrsx=9Wsw1wps9S)T-0%x=Z>jRf<1J=h(Wn9iJMg&2- zH3b+y=9eJ2l@EnlEfFP8ZOp1nciMMBFla+D)mL~H!TYt^rNI*G9MNeg%AbrWE)dOL z#*(BBNk&Kcvu=s3R5Vt(X*f^Yw0_6NgF>|fp6E9yB_-*n9+$12_)ovmax2Y~(C@W& cJ^ris4FfUNXW;zY0ssI207*qoM6N<$f>gjW8vp$`(qbpA4*H?-JHP{ZGD+zUsJ2^ zJcpbbS$T(mHMMLT1+1+VTN4!Z9;r@HP`o+gY~R_A-PYD?W*j4NX-!#XlHoOBzTGy(zPCH{ihTNwXH{uEkm-CPa+@JvNO9tudX_g=SY;Tr z&PYFy;#4RsjZnIp6}IlsywqFu%=7A4_2gzAHVQKfx!5Ii@5;Kaeg^>sj*JcGoFrv7 zG@sXEY+|$X{!tOqJ?r8_G4?AfgSF+G_^K5+Ij>!MP|ku_QG` zp**uBL&4qCHy}kXl^rPl)YHW=#NzbZD;vEI2Z$W|7;n<8srXi5OGkve`a#DFcg+Rn zpT5doAi(Z==jz(k`WIZ;V_j}Jux`D+v^&#DNw4YeyRskup3f`RuleH?_&6&gse4PV z2iLRCg@uvL?_XI7dY#+2q;8_^;h-OZ+cSEfKTv00y-Z0vX-~CqfpY#Qli7SrjM}#q zyg%jf?ojdP`V%o+O~H{zudo8=olqvh#kKmuuJl+9-b~>a$__$*qNx8Q$EurpNN<#DOF8FN$B` V@h)NKFpdX>gr}>Y%Q~loCIGGfnBD*Y literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/J.png b/chatto/src/main/resources/pictures/J.png new file mode 100644 index 0000000000000000000000000000000000000000..2f796edee476dc23f232c4501b7e013fa67b63a4 GIT binary patch literal 439 zcmV;o0Z9IdP)WFU8GbZ8()Nlj2>E@cM*00AFKL_t(I%k9*?E`&iC#qpoT zW@8IkpAm^hr4Z2(By>9B5=5h=5~b2bkf_~&XoO1K01>g#5DFjJPl?gI#ah$usAfd1#f|YWNA=GCC@pk_W|jhmJXvQ;?5V!Lu#Nc<{QiP_^#$SC{v*vMv?n z<&=e}?2GEM+iSqLuH3nB=H0MP?A!LxXrybN1i{Qtsn%06>PYJQ-bB;Qj0O9L{~+9D z(nxb~pB={zEHncTS<++5T!k0n!odfU1}k24I{LQn_23uRVA^ctWX$Wvn|BnXc5*3V$v`M22>lr4Xmq&2nZstPUGl8)QH345|$ym|HL#EQs0 literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/K.png b/chatto/src/main/resources/pictures/K.png new file mode 100644 index 0000000000000000000000000000000000000000..3f16ebf1f1c6bb6bd71e224d062c8fad4b71f44c GIT binary patch literal 582 zcmV-M0=fN(P)WFU8GbZ8()Nlj2>E@cM*00FK^L_t(I%e~b*XjDNI#_`{7 zR*b6=6B`pGHoictObWrmRt1r0qbbBnL=+p*P7A>*pp9T4A{arjQL#{KlO}>7C{@u0K0UtrQ-iLz zS585QDakt{_~v+3JZq;{f-knlZS=Y)2(DRC5nbw#NkI@CtXwhXx9oeR6Ruc;Ib+7k z$+?DG+U>Ms+VR^Xr@W1)u5&+;-2pbasVE3$T(vsAP8rT)yT^&sf^(KN61e~m>$J;6 zg1&aN34zr-i=QU81(~Ld)z8l zuZD|$f?tXi(T|Qe?~{Oyx?-}IIBd!b`|Hf@_S|Q~bsvh-Gq5(=)h?UU(8U{n0g-*I UC;g!K2LJ#707*qoM6N<$g33?(FaQ7m literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/L.png b/chatto/src/main/resources/pictures/L.png new file mode 100644 index 0000000000000000000000000000000000000000..46879415c6bd2932db6678288fe4057f4ab72b22 GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP-6T7f6(|)my2|yv)%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dDE{2j#WBR=_}goHy_g+E+8!P^)W{MD^i;pWV$FTfO?&+T z?~i(W)+OZTif-H_*yPxp^e1w5bBk!xZfT{)pAQUz&zz~QTlOPDLD$vfQqj@RxiX3T z{uQ&smx?oLC+!dVzA@zC8@)dD1oanZnCEzH_VrDh`|ovoS^--+1byg_;(aIfLnm@^ zn8g&+oCM+5I}RUSbD(`k&$_n>+753U*18FC*3NlcCibAYWB-co%ex-*YnE-Bv8nE% z-1X9ETL=F18^8O5_DJ#(hVUJl)lXO5tFlkC;yU5oa9nt5+TTm&X>r=Y THMzB2ps?_C^>bP0l+XkKg71@U literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/M.png b/chatto/src/main/resources/pictures/M.png new file mode 100644 index 0000000000000000000000000000000000000000..79b99a369c0bce3283f9f0aecbce7a2808bef2a8 GIT binary patch literal 911 zcmV;A191F_P)WFU8GbZ8()Nlj2>E@cM*00Q$#L_t(Y$K}>bZ1%wSEE0%r&6XMoy;+J3n#t=4M5<;TkBBF?jMDP}Z zNCXO66ts17ra2kk_wB!l3ldNACNuNSnP=vi=bSU#r}jX6|GD2l$G^o`kL9?HzxxDT zhz(eYTli}}JAMdnVH!W+^?CTdf$O-7H|I;$GdP2p_zqyW55M8c@6%x5U)F0`Ua5U0T1GZ)c+lqQ-8Eld$HFl^}d5YQbL)S3wRO6Q~z5WO8t?Buv&m!SQ7o~ zcpJZT39Q#3tg&d%;5-iFYKreovB6>!L3I((hxjafXJUM`zM@_w{S_gia!(iV1x}}V zV^xxPwW9f8_?}Alyb5dDfO{c=k{WN}Yy6DkY2Ky?Euq#|BDceLI1%j=k?TeLZ)pP_ ztbR`7%V>X&UsIwe31)jGw!^p%7nm7_P z!xem=a9U5iG%5BTE=4$r(s-((dr#bVsXm|Jt3>a;s7^&cYtklXf$JYrIG2F##EEYt zvFxvM`nwTI*^ajyY!DS0dD=;#!W?6Gp1?)@4s) lXWI+1WFU8GbZ8()Nlj2>E@cM*00F{DL_t(Y$L-d;Yg9oL z2Jqi*f{%a@NfBaWB_@?Nv5{b7A(BE3h}bIwt&iV?91 zLJ%WLNZf047A{M6GvnPW7Jl6v=A7@mXZT0asG!&Ebzz`~;xP7MC%&UdBpt*-Okf3T zIUWz=dXT-s@eJJ)L0I5+kKi`Nek_G@ffo^d3Lao1^p>%EvrEmy8cks~(wFxU-CV;Xpa%GuT#Va}D4*Q z9CH|}i@1%SxQk~c*(sdGjyh6Z9;qeVz`IfzF5ys}5x4OK53yR>YFA>h6m=4}a0|~% zvZFEILn-8g36Dh4#n2aAizjp`k#-l~~O7Q~b)YnMz!2 zF=AKo_HT4`Ctk-NZKdDDDI;L+xWFU8GbZ8()Nlj2>E@cM*00KZsL_t(Y$L-cRXjM@V z2H-EzxW}j@ir|J2F)kRe(kg{uBWPo#MG&;G5*tCS>@4iG77IZT3o!~J2rfYgqD8QX zHqjz+!;olVR-es0I6Ur4?u`#^{NWboa%TQHv-6KoS0mZd-ymLuMzmlhCh-+R_=#V& z_D%z~;{sk`G+sJ`LEOZFFsf!~L?`-!tlBq)%UE9v($%<#u?m7Ae8e|QR~T>uYiCos zU=Gq{IEhPG68{GA5Z7@FJ(!4?FONHW!np1TMIqGVP`s-Al3pCg!i>A8@gceU6CLw$ z@n&=-iu!PP4w*9;PGs&z739W397_}o<6KU;u5fQDGJkgw;&r%_5I@3}oU%tSlE|K| z7RM}XOyV<&r|8c~{1^jCH`||=xB+cg6XyZEDB!J;q~2j`Mlxm+FAic!`ScY`%=Za# zQ&xgid-0~^JcQ2$iN_P-`YfLMDov#+IgdqyDB5D{5+kPa5ZC?TxD*vo*6}IJ@GRo# zsN1w{E-gU13ayFmH+i#qs-kpS3Qn7jiUIK?FY$PVl^f7rfVeZfB>MdGA~9-@*pvtT zwVZK38rw*@Yh5jxFU>Lb$_n1aq?!$Fcpa-xDavr?e0H&e$$C?YSnZv~L*2=6D8-7j zU_K?W23IOJi(Z_HxHn;25cDT6E#ZD_F7rk!9#oLe#JV$p?-_g7vjUTBR_wsN3O8nb zleis|t!8M(dA!AsD7lGPZ$|JECyF$hqOGw7`>+cu@FjNao-pD+s^T|9BeHA8{fBA* O0000WFU8GbZ8()Nlj2>E@cM*00FQ`L_t(Y$L-d^OBGQR z2Jl~6Y7r$_1`&Zl3-d21$&Jt|+O}0%8U#VRAn0GHwyh``vr)Io~<_Z`ha9rUyrH2pwpkj?EaqW7Ol{j@X~W zrVlqUjw3;wU<(^q#RBH=8uKyttEg8l#KK9=@d@{E9tVmLU&5Ev!khRJC)x_vw%WjF zTtj!eu1+JtG+yCgxV{f3(I3ylVLO0F5hIgDPuz(6&Y~9=aThZYXw8!^;B*xd&Oyqkd#MgtoseEsXrc)l38^Z*; zVy=egWf0dh-<>#z5nRS-q*oL7@v#izL0rWtoC%>Az*(Hg*gnLAi0s0WS;Jzg5VVrZ zW!wriQ-);1u^6qeh$nc8*}qk&qQUh&ioqL9;T@J@zHmLxViJ!-nZDv1*6}kmIv3ih zPIGt``I)rqs&)iqJGBW_5|@HK;#O#+TGk|Q`&tI^3O>a9a)?AF4LuCuN^}=5qto2O aKj9A~2Z4$DGSD#q0000P)WFU8GbZ8()Nlj2>E@cM*00N>(L_t(Y$K}^Kh*Vn; z2H0vZ?yYM>^An3-wpOCG#HA5Toxzzq{I5k&1WRxPU(nmna~>-&bReL^u>}uvhxbHk z+tE<~{u-Mrfmcd(BevoICd679t+g<}|BrT-?pOZ?wf8}*zV znQmffjifvse8`BnyPDG3mVlPTYM8w%I>y9$2xA}}r z1gaC?Dn*>CS;GLNb{r-q@9%@Cg}}X`yB!I`|ELpqIsQz}eUR@~`H0)`GKqLf{Q6pL zTWEa-e5F5!(KwaV@w4c^MK%wIa0086^%cSB?2MO~+#m4J=qQ;}H;xuG1^6$RTMqW` z!%SRGRI3;Jt4-4^+)Er^VQ0am~N;!g#uXmsy z7WcUX(?WdS;!X?;pK?>oo*-#+kg(-v%-Yr3ybzbhU*^BlcLPb}WFU8GbZ8()Nlj2>E@cM*00J3FL_t(Y$L-cjh|WWX#W|h-|2gk--uF2#{|J*Z?rABpo=-;8iDzIv)}Sr+|G`gu z!2tU489y+Bv1TZ;D+uxx##i*>Dt2Oi8HF;V1qZ8;mKyi48#C)Ao{YoE4@dA3eRvxZ z4~E3Wyh}oEfF08;>EXU$Fa9c<}h+FkUd?<`2rPpa4_QyOI z5k14}cnCU*{?UbLcvMu&4N3hb<{fdrUGe_jDU$yeE=2-1h4>Zrl#ssz=b|CVC)wYf zv9-!XythKGoC*00`-dv*Z<=%Ea2_`!^TnHe84JhTGIKZ;nO$i3R>#`7d-V`b;#0DA zBs!GL#QA7t(};2z$e-YPB6)ilN=?{=r%@s|!XC4rJvx$Myuh-08vdo&5nf#jW<`&- b#DCx~zhs~x!3zS000000NkvXXu0mjf>M<WFU8GbZ8()Nlj2>E@cM*00L`CL_t(Y$K}^ch>l?t z2k_tAU1o?hhB5BaTuf4~WzwW9C^jry%0f1>P!_VWkd+M!D=Q)!rEF{@vQQ!)3#Z<`-oEE~{^vaBfBxsZ{39!yz`bLW1zd(IOu@`}rUl>+ ze2w1$j7-$cRHFsQaR(nU6rf`P_7>N%7Y!&c6|@$I@FoG2@41V$rKGYNhtZdIa34P5 zJwD(IhSS!(z~XENSLOk3!I8)%4B|O%;|V^;8B5TCEtnsXwqPI5j5peH97v1e7Fu)c z*&og%*Oz5GJey5TyQ*H?h%3xyS8zY^-jfZuGEc-ciTB}Paz49&ir~jZjE?uJYj8Ai zZKDyO<6=Jasb#Z0OuqdMM3Bkx@KZ5y10mHY5d~*O-qYazTj^&|X zeZhf(Eg$e8x&pKr%6L>Fn8ws+*pXvTD;|c_ruG~wCdUh6hDw5W;~I8jRR~9C zkm_BeCbhOJcv6(L<6efDYBb8FKPJkt3=VE9wM|(Ye%bh%wvnTB@OL64!z#yn_kiL?EbueRt|ev{xk|!&6xgW?VYG8dm1&5g&xq~ zanRVxu!c4BvCQ&9^Fz)$(b^HU@@4N`VtMvOR!W}^x#Y;SBeGys-{Sua;eV&qb4@!a zIR}jP%zuz{Y}WE67eqd&6kSVqn15>1XTQnLFIdC{KfL>(mfgIMfxp69spR=rYfun) My85}Sb4q9e0Q&io2LJ#7 literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/U.png b/chatto/src/main/resources/pictures/U.png new file mode 100644 index 0000000000000000000000000000000000000000..70c1adb71d6b6a031e76be280bf61d54a697de8a GIT binary patch literal 656 zcmV;B0&o3^P)WFU8GbZ8()Nlj2>E@cM*00Hz#L_t(Y$L*FuOItw@ z$A3u^Q_`lfi4l4#1))MwM5N%MC%;2atzSX#;GsQvv8R5Pdgv`T4<0-ON(pMI2M?x& zAVu1SBqndY%!6S`UgmAQ=^qx}K6d86GyC6}g}a6aWBwO_r$8RK23$(N8K4Z5fT4_u zC<0r+Pv9%C8W7M+pbq>1KFCHUVgYDLlzre+0P&vKJKz$S^A}`C80CS7^6ez;X;D(? z%>j=r#2J+u2Mhv;haN}HLY!6qomChfoae7#jaU;$0mPB#FP}Em1YD)0KoW^>18!2i z$c`~`-j1h@7%Opd^RBkq^Q0Jz<&#IALuCJ z#{tAo)Y*2e#66&`=4Dkvt(aBBS1OQ1;x2Hm<`tlpQdm_rZ;MY;#7#AymyTscO}bGW z@!6;%G%n(XnmoV>GPycnF-zLm(E)x}2K@Zul!4ExinV}u2D=U5 zm$Ewy5GHR#7~09}g!Rjk)=1gB52>*wU{^VDz&Bu1#1p?%o&(z=B28KsA3=BleDTN! zz=dq$1o-0-I?>0gX2_upD>i=<%F}MLgZiFxjPw%Y&0000WFU8GbZ8()Nlj2>E@cM*00JdRL_t(Y$K}`Ei%nq^ z2JqjU(HQbk2sIKjk|72oT#-l-Zn$&d-nIY5KR`&yM+)P@40B-^q$o{OLq1AK`Dg|+ z$IaS$JDu6H_jE?ds#EVed%f>-_OqY$>~;9xqIrsIEoj0-$#a`98J?RT?s#sj+pw<$@NRS``LZ&=Z}BA7 zv#<#(3j!ZUYph3cwG8kF+)VPd*iis<6?Vmsg@+g@1N;?t6H&Ak1U`zTNqz=nWm|1E z`Vx`!pd+Wg6}_SUD&AnlWW|-7gWltIl6TgKrk&4R;X#sbLwk*=)+eI6oF}R>z^~Dtwx|4zRFsLFmb31B9#00000NkvXXu0mjfnkFa) literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/W.png b/chatto/src/main/resources/pictures/W.png new file mode 100644 index 0000000000000000000000000000000000000000..16f7fcacdbaad5919b1adfa1007102d41246bb1b GIT binary patch literal 755 zcmVWFU8GbZ8()Nlj2>E@cM*00LV{L_t(I%jMKtYgAVl2H+<% zc7pMcs1uJA1D@h|o}?;9tS!AL#eyQ}O%c5H!av|2@X9|SctTVJk%$@#;@Q!PNt#gF zl45J4m;;)QJBQ`P>+|2Pf@6n)M`{Kn!?w~ zTVc>BY}W3EwkiMLufti*Q?mXfGT=yQa6%AVsS-81A_$JjROP)Vh@2vYK9KWeg=kBh zbGwELeuEZ_xKB!O-ypK~RESzMfgKtv_=7TnzuYWke&rG7__kuvTh)TKno4^M>g~sv z+XhPJ$aQ3FwWuu0*%YIcyt*>qW;H&zSejzO&x%-XXQJZO3B&v{J}fQh@o>@{{OK3e z+Z#)=wyDK$2GC+j%rEmD#$6Rnniqr>&#==dA}knXBPP05)8RKSrLrt5U|oV zuu(I5T-6gVyj1leX*|}QG*9T&jYfO0P+kU4yztU6cAJl!R*btq(#()sn5Pv@wxg(^ z=&63Jw-N_5;9u?4=Av6*m5tWOp+_fPc!e*tnrojlZu-00Jg!S0mijTi9tFL4r%Qx3 z-&m){xO3CJ(FINmK6>i0py+taAJs4T!!?nhTm5wAjH7~JR3v!m>+)Ue6pSeff|E1# zhrBmJ{GBO3hqP-ZVm9(s(vV8ve>{j$@g5Z+H2{!AQAQ-Z?Ql6?9^(o+^=lT=uTV2M`5%-)KqRw&e-L-2WFU8GbZ8()Nlj2>E@cM*00J*bL_t(Y$K}>dh>c+s z2k;-m%rHufX_6G9OVeZ_Ns^BcVI#ywDOph#3Jb(a7B+S$HEfho9V|GJC&yyt(;`<(Zj|2h1Fm{3h0HARePActBEB40+( zCM?7VeqGEQPB;=RS@Qod0QcHm>=@Fg~ z6~P?bED-n@>Pt;wsTw@Lzd@2Qa3kzve?@Ir_Lh!Q16r`YNEiHv_yb}`#TZuUUl{-Z002ovPDHLkV1l+6H-rEH literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/Y.png b/chatto/src/main/resources/pictures/Y.png new file mode 100644 index 0000000000000000000000000000000000000000..e998b80bbd3033a24dc10153ec466567363acec8 GIT binary patch literal 655 zcmV;A0&x9_P)`>03B&m zSad^gZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00Hw!L_t(Y$L-clh)!V? z2k_s_m}HDM%!+(Vm?;Y@N^E>ACXyAUL>AagR+NREl!y`wWudG@l6+)=B@1FPBsM}K zpBW_PwK=z*E@SR}-?=vY>(>1^=l_4sx#yhc;a@}B5bxVD7A^SE1a~J!;d>E>PHe{w z>_x3X;WfC1lbBk>VF5m&jt}T-Ky)^q21gzH@&>KHaT>%x+}E%H-6cd@up3hnzHdbw z$KgP-+OP54T2Ocu-Y5Q#Fr(t+xVsY|)0xrgc%WSqp7Mj8s?8z0{VT_OOeYxLpAr>sApq7n9v}y5d%?*aLIG2pqI`%ixKEq)WPA4J*G1ao?`=pdk+h% zPH|U6mx&I>4%S|^@J75%gwJDU)uMB8IT3x0b${a+ygqiOO!!E%^SEeLe~h+)J2(?_ pdBhL&c|4Ef>MI<@P5)66KLJpzn}d`niYfpA002ovPDHLkV1mxi7i$0j literal 0 HcmV?d00001 diff --git a/chatto/src/main/resources/pictures/Z.png b/chatto/src/main/resources/pictures/Z.png new file mode 100644 index 0000000000000000000000000000000000000000..ab84cd4f5df27e602352062bf1f90e2aa09928d0 GIT binary patch literal 568 zcmV-80>}M{P)WFU8GbZ8()Nlj2>E@cM*00EpyL_t(Y$L-d?OVv>r z$MM&_=8se)2O+J&h8&y}iNL`jXt1Rg2Umw&Z1)eS2s zoAAw~9y|-cTF(p`vVw=ORVVOzsNO!EIsm+i7Z^FnqY5gb38%{}e21UaG*>T1(VD^I z()|q#M?bWTalB4BAwzf{DpbT8#!`=E9~L5C|6n>bv}`hfT&*!v!)MG_8|b^k$sG}1z!z-zFYEyRepeat password: + + +
+ + +