commit 480140a7b98b4530bc293f858b0be91cd0a2bf2b
parent 3eda987d1a7167154b615a2933ca3062bc746d90
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Wed, 29 Mar 2023 01:54:17 -0700
work for dif 3
Diffstat:
| M | src/lechatphp/mod.rs | | | 228 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
1 file changed, 224 insertions(+), 4 deletions(-)
diff --git a/src/lechatphp/mod.rs b/src/lechatphp/mod.rs
@@ -1,4 +1,5 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
+use std::hash::Hash;
use base64::{engine::general_purpose, Engine as _};
use image::{ColorType, DynamicImage, GenericImageView, Rgba};
use lazy_static::lazy_static;
@@ -88,10 +89,13 @@ fn get_letter_img(letter: char) -> Option<DynamicImage> {
pub fn solve_b64(b64_str: &str) -> Option<String> {
let img_dec = general_purpose::STANDARD.decode(b64_str.strip_prefix("data:image/gif;base64,")?).ok()?;
let img = image::load_from_memory(&img_dec).ok()?;
- solve_difficulty2(img)
+ if img.dimensions().0 > 60 {
+ return solve_difficulty3(&img);
+ }
+ solve_difficulty2(&img)
}
-fn _solve_difficulty1(img: DynamicImage) -> Option<String> {
+fn _solve_difficulty1(img: &DynamicImage) -> Option<String> {
let mut answer = String::new();
for i in 0..5 {
let sub_img = img.crop_imm(5 + ((8+1)*i), 7, 8, 14);
@@ -113,7 +117,7 @@ fn _solve_difficulty1(img: DynamicImage) -> Option<String> {
Some(answer)
}
-fn solve_difficulty2(img: DynamicImage) -> Option<String> {
+fn solve_difficulty2(img: &DynamicImage) -> Option<String> {
let on_color = Rgba::from([252, 254, 252, 255]);
let mut answer = String::new();
for i in 0..5 {
@@ -133,4 +137,220 @@ fn solve_difficulty2(img: DynamicImage) -> Option<String> {
}
}
Some(answer)
+}
+
+#[derive(Clone, Debug)]
+struct Letter {
+ offset_x: u32,
+ offset_y: u32,
+ character: char,
+}
+
+impl Letter {
+ fn key(&self) -> String {
+ format!("{}_{}_{}", self.character, self.offset_x, self.offset_y)
+ }
+}
+
+// SolveDifficulty3 solve captcha for difficulty 3
+// For each pixel, verify if a match is found. If we do have a match,
+// verify that we have some "red" in it.
+//
+// Red circle is 17x17 (initial point)
+fn solve_difficulty3(img: &DynamicImage) -> Option<String> {
+ let image_width = 150;
+ let image_height = 200;
+ let min_px_for_letter = 21;
+ let min_starting_pt_red_px = 50;
+ let mut answer = String::new();
+
+ let mut letters_map: HashMap<char, Letter> = HashMap::new();
+
+ // Step1: Find all letters with red on the center
+ for y in 0..image_height {
+ for x in 0..image_width {
+ let letter_img = img.crop_imm(x, y, 8, 14);
+
+ // We know that minimum amount of pixels on to form a letter is 21
+ // We can skip squares that do not have this prerequisite
+ if count_px_on(&letter_img) < min_px_for_letter {
+ continue
+ }
+
+ // Check middle pixels for red, if no red pixels, we can ignore that square
+ if !has_red_in_center_area(&letter_img) {
+ continue
+ }
+
+ for c in ALPHABET1.chars() {
+ let good_letter_img = get_letter_img(c).unwrap();
+ if !img_contains_letter(&letter_img, &good_letter_img) {
+ continue;
+ }
+
+ // "w" fits in "W". So if we find "W" 1 px bellow, discard "w"
+ if c == 'w' {
+ let capital_w_img = get_letter_img('W').unwrap();
+ let one_px_down_img = img.crop_imm(x, y+1, 8, 14);
+ if img_contains_letter(&one_px_down_img, &capital_w_img) {
+ continue;
+ }
+ } else if c == 'k' {
+ let capital_k_img = get_letter_img('K').unwrap();
+ let one_px_up_img = img.crop_imm(x+1, y+1, 8, 14);
+ if img_contains_letter(&one_px_up_img, &capital_k_img) {
+ continue;
+ }
+ }
+
+ letters_map.insert(c, Letter{offset_x: x, offset_y: y, character: c}); // Keep letters in hashmap for easy access
+ break;
+ }
+ }
+ }
+
+ if letters_map.len() != 5 {
+ return None; // did not find exactly 5 letters
+ }
+
+ let mut starting: Option<Letter> = None;
+ // Step2: Find the starting letter
+ for (_, letter) in letters_map.iter() {
+ let square = img.crop_imm(letter.offset_x-5, letter.offset_y-3, 8+5+6, 14+3+2);
+ let count_red = count_red_px(&square);
+ if count_red > min_starting_pt_red_px {
+ starting = Some(letter.clone());
+ break;
+ }
+ }
+
+ if starting.is_none() {
+ return None; // could not find starting letter
+ }
+
+ println!("STARTING IS {:?}", starting.clone().unwrap());
+
+ let mut code = String::new();
+ let mut letter = starting.unwrap();
+ let mut visited = HashSet::<String>::new();
+ for i in 0..5 {
+ if visited.contains(&letter.key()) {
+ return None; // already visited node
+ }
+ code.push(letter.character);
+ visited.insert(letter.key());
+ if i == 4 {
+ break;
+ }
+
+ let mut retry = 0;
+ 'the_loop: loop {
+ retry += 1;
+ let square = img.crop_imm(letter.offset_x-5, letter.offset_y-3, 8+5+6, 14+3+2);
+ // let red_px_pts = get_contour_red_pixels();
+ // square.save(format!("captcha_{}.gif", letter.key())).ok()?;
+ break;
+ }
+ }
+
+ Some(answer)
+}
+
+#[derive(Debug)]
+struct Point {
+ x: u32,
+ y: u32,
+}
+
+// fn get_contour_red_pixels(img: &DynamicImage) -> Vec<Point> {
+// let top_left_pt = img.Bounds().Min;
+// let bottom_right_pt = img.Bounds().Max;
+// for i := 0; i < img.Bounds().Dx(); i++ {
+// pxColor := img.At(top_left_pt.X+i, top_left_pt.Y)
+// if pxColor == redColor {
+// out = append(out, image.Point{X: top_left_pt.X + i, Y: top_left_pt.Y})
+// }
+// pxColor = img.At(top_left_pt.X+i, bottom_right_pt.Y-1)
+// if pxColor == redColor {
+// out = append(out, image.Point{X: top_left_pt.X + i, Y: bottom_right_pt.Y - 1})
+// }
+// }
+// for i := 1; i < img.Bounds().Dy()-1; i++ {
+// pxColor := img.At(top_left_pt.X, top_left_pt.Y+i)
+// if pxColor == redColor {
+// out = append(out, image.Point{X: top_left_pt.X, Y: top_left_pt.Y + i})
+// }
+// if img.Bounds().Dx() < img.Bounds().Dy() {
+// if img.Bounds().Max.X == 150 {
+// continue;
+// }
+// }
+// pxColor = img.At(bottom_right_pt.X-1, top_left_pt.Y+i)
+// if pxColor == redColor {
+// out = append(out, image.Point{X: bottom_right_pt.X - 1, Y: top_left_pt.Y + i})
+// }
+// }
+// return vec![];
+// }
+
+// give an image and a valid letter image, return either or not the letter is in that image.
+fn img_contains_letter(img: &DynamicImage, letter_img1: &DynamicImage) -> bool {
+ let on_color = Rgba::from([252, 254, 252, 255]);
+ let red_color = Rgba::from([204, 2, 4, 255]);
+ for y in 0..std::cmp::min(img.dimensions().1, letter_img1.dimensions().1) {
+ for x in 0..std::cmp::min(img.dimensions().0, letter_img1.dimensions().0) {
+ let good_letter_color = letter_img1.get_pixel(x, y);
+ let letter_img_color = img.get_pixel(x, y);
+ // If we find an Off pixel where it's supposed to be On, skip that letter
+ if (good_letter_color == on_color || good_letter_color == red_color) &&
+ (letter_img_color != on_color && letter_img_color != red_color) {
+ return false;
+ }
+ }
+ }
+ true
+}
+
+fn has_red_in_center_area(letter_img: &DynamicImage) -> bool {
+ let red_color = Rgba::from([204, 2, 4, 255]);
+ for y in 5..=std::cmp::min(letter_img.dimensions().1, 7) {
+ for x in 3..=std::cmp::min(letter_img.dimensions().0, 5) {
+ let letter_img_color = letter_img.get_pixel(x, y);
+ if letter_img_color == red_color {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+// Count pixels that are On (either white or red)
+fn count_px_on(img: &DynamicImage) -> usize {
+ let on_color = Rgba::from([252, 254, 252, 255]);
+ let red_color = Rgba::from([204, 2, 4, 255]);
+ let mut count_on = 0;
+ for y in 0..std::cmp::min(img.dimensions().1, 14) {
+ for x in 0..std::cmp::min(img.dimensions().0, 8) {
+ let c = img.get_pixel(x, y);
+ if c == on_color || c == red_color {
+ count_on += 1
+ }
+ }
+ }
+ count_on
+}
+
+// Count pixels that are red
+fn count_red_px(img: &DynamicImage) -> usize {
+ let red_color = Rgba::from([204, 2, 4, 255]);
+ let mut count_on = 0;
+ for y in 0..img.dimensions().1 {
+ for x in 0..img.dimensions().0 {
+ let c = img.get_pixel(x, y);
+ if c == red_color {
+ count_on += 1
+ }
+ }
+ }
+ count_on
}
\ No newline at end of file