Skip to content

Instantly share code, notes, and snippets.

@MikuAuahDark
Last active January 23, 2023 05:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MikuAuahDark/680d203795ce4f2b9843651cb9dc3e10 to your computer and use it in GitHub Desktop.
Save MikuAuahDark/680d203795ce4f2b9843651cb9dc3e10 to your computer and use it in GitHub Desktop.
Rust code to check if PNG is KoiKatuChara or KoiKatuClothes
// Code to check whetever PNG image is normal PNG image or it also contain
// Koikatu data like character or clothes.
// Copyright (c) 2023 Miku AuahDark
// You can use portion of this code or as a whole without my permission.
// clang++ -std=c++17 -D_CRT_SECURE_NO_WARNINGS koikatu_check.cpp
#include <cerrno>
#include <cstdio>
#include <algorithm>
#include <array>
#include <filesystem>
#include <memory>
#include <system_error>
#ifdef _WIN32
#define strcmp_i _stricmp
#else
#define strcmp_i strcasecmp
#endif
void readFile(void *buf, size_t size, FILE *f)
{
if (fread(buf, 1, size, f) != size)
throw std::system_error(errno, std::generic_category());
}
std::string detectType(const std::string &path)
{
constexpr std::array<unsigned char, 8> PNG_SIGNATURE {0x89, 'P', 'N', 'G' , 0x0D, 0x0A, 0x1A, 0x0A};
constexpr std::array<unsigned char, 4> IEND {'I', 'E', 'N', 'D'};
std::string pngtype {"Normal PNG"};
// Open file
std::unique_ptr<FILE, int(*)(FILE*)> f {fopen(path.c_str(), "rb"), &fclose};
if (f == nullptr)
throw std::system_error(errno, std::generic_category());
// Read signature
std::array<unsigned char, 8> buf;
readFile(buf.data(), 8, f.get());
if (!std::equal(buf.begin(), buf.end(), PNG_SIGNATURE.begin(), PNG_SIGNATURE.end()))
throw std::runtime_error("Not PNG");
// Loop until IEND
do
{
constexpr std::array<unsigned char, 4> ACTL {'a', 'c', 'T', 'L'};
readFile(buf.data(), 4, f.get());
// Chunk size
size_t chunkSize = (size_t(buf[0]) << 24) | (size_t(buf[1]) << 16) | (size_t(buf[2]) << 8) | size_t(buf[3]);
// Read chunk name
readFile(buf.data(), 4, f.get());
// Skip chunk and CRC
if (fseek(f.get(), long(chunkSize) + 4, SEEK_CUR) != 0)
throw std::system_error(errno, std::generic_category());
// Check APNG
if (std::equal(ACTL.begin(), ACTL.end(), buf.begin(), buf.begin() + 4))
pngtype = "Animated PNG";
} while (!std::equal(IEND.begin(), IEND.end(), buf.begin(), buf.begin() + 4));
// More guesswork
size_t readed = fread(buf.data(), 1, 8, f.get());
if (feof(f.get()))
return pngtype;
else if (readed != 8)
return pngtype + ", unknown data";
if (buf[0] == 0x64 && buf[1] == 0 && buf[2] == 0 && buf[3] == 0)
{
switch (buf[4])
{
case 0x12:
return std::string("Koikatu character inside ") + pngtype;
case 0x14:
return std::string("Koikatu clothes inside ") + pngtype;
default:
return pngtype + ", unknown Koikatu data";
}
}
else if (buf[0] < 0x64)
return std::string("Koikatu scene inside ") + pngtype;
else
return pngtype + ", unknown data";
}
void printFileStatus(const std::filesystem::path &path)
{
std::string result;
try
{
result = detectType(path.string());
}
catch (std::runtime_error &e)
{
result = std::string("Error: ") + e.what();
}
printf("%s: %s\n", path.string().c_str(), result.c_str());
}
inline int usage(char *argv0)
{
printf("Usage: %s [-r|--recursive] <file1|dir1> <file2|dir2> ...\n", argv0);
return 1;
}
int main(int argc, char *argv[])
{
if (argc < 2)
return usage(argv[0]);
bool recursive = strcmp_i(argv[1], "-r") == 0 || strcmp_i(argv[1], "--recursive") == 0;
int filesArg = 1 + recursive;
if (recursive && argc < 3)
return usage(argv[0]);
for (int i = filesArg; i < argc; i++)
{
std::filesystem::path path {argv[i]};
if (std::filesystem::is_directory(path))
{
if (recursive)
{
for (const std::filesystem::directory_entry &p: std::filesystem::recursive_directory_iterator(path))
{
if (std::filesystem::is_regular_file(p))
printFileStatus(p.path());
}
}
else
{
for (const std::filesystem::directory_entry &p: std::filesystem::directory_iterator(path))
{
if (std::filesystem::is_regular_file(p))
printFileStatus(p.path());
}
}
}
else if (std::filesystem::is_regular_file(path))
printFileStatus(path);
}
return 0;
}
// Code to check whetever PNG image is normal
// PNG image or it also contain Koikatu data
// like character or clothes.
// Copyright (c) 2020 Miku AuahDark
// You can use portion of this code or as a whole without my permission.
use std::fs;
use std::fs::DirEntry;
use std::fs::File;
use std::fs::Metadata;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::path::MAIN_SEPARATOR;
fn detect_type(path: &str) -> String
{
// PNG magic byte
const PNG_SIGNATURE: [u8; 8] = [0x89u8, b'P', b'N', b'G' ,0x0Du8, 0x0Au8, 0x1Au8, 0x0Au8];
// Open file
let mut f: File = match File::open(path)
{
Err(why) => return format!("{}", why),
Ok(file) => file
};
// Read PNG signature
let mut buf: [u8; 8] = [0u8; 8];
match f.read(&mut buf)
{
Err(why) => return format!("{}", why),
Ok(n) => {
if n != 8
{
return String::from("read error");
}
}
};
if &buf != &PNG_SIGNATURE
{
return String::from("not PNG");
}
// Skip all chunks until IEND
loop
{
// Read chunk size
match f.read(&mut buf[0..4])
{
Err(why) => return format!("{}", why),
Ok(n) => {
if n != 4
{
return String::from("read error");
}
}
};
// Chunk size from big endian
let chunksize: u32 =
((buf[0] as u32) << 24) |
((buf[1] as u32) << 16) |
((buf[2] as u32) << 8) |
(buf[3] as u32);
// Read chunk name
match f.read(&mut buf[0..4])
{
Err(why) => return format!("{}", why),
Ok(n) => {
if n != 4
{
return String::from("read error");
}
}
};
let end: bool = &buf[0..4] == b"IEND";
// Skip data and CRC
match f.seek(SeekFrom::Current(chunksize as i64 + 4i64))
{
Err(why) => return format!("{}", why),
Ok(_) => ()
}
if end
{
break;
}
}
// More guesswork
match f.read(&mut buf)
{
Err(why) => return format!("{}", why),
Ok(n) => {
if n == 0
{
return String::from("normal PNG");
}
else if n != 8
{
return String::from("normal PNG, unknown data");
}
}
}
if buf[0] == 0x64u8 && &buf[1..4] == &[0u8; 3]
{
return if buf[4] == 0x12u8
{
String::from("Koikatu character")
}
else if buf[4] == 0x14u8
{
String::from("Koikatu clothes")
}
else
{
String::from("Koikatu data, unknown")
}
}
else if buf[0] < 0x64u8
{
return String::from("Koikatu scene");
}
else
{
return String::from("normal PNG, unknown data");
}
}
fn print_file_status(path: &str)
{
let status: String = detect_type(path);
println!("{0}: {1}", path, status);
}
fn scan_dir(path: &str, recursive: bool)
{
match fs::read_dir(path)
{
Err(why) => println!("{0}: {1}", path, why),
Ok(dir) =>
{
for entry in dir
{
if entry.is_ok()
{
let dir_entry: DirEntry = entry.unwrap();
let subpath = dir_entry.path();
if subpath.is_file()
{
let filename: &str = subpath.to_str().unwrap();
print_file_status(filename);
}
else if recursive && subpath.is_dir() && subpath.to_str().unwrap() != "."
{
let filename: String = format!(
"{0}{1}",
subpath.to_str().unwrap(),
MAIN_SEPARATOR
);
scan_dir(&filename, recursive);
}
}
}
}
}
}
fn main()
{
// Get argv
let argv: Vec<String> = std::env::args().collect();
let argc: usize = argv.len();
if argc < 2
{
println!("Usage: {0} [-r|--recursive] <file1|dir1> <file2|dir2> ...", argv[0]);
return;
}
// Check recursive flag
let dir_recursive: bool = argv[1] == "-r" || argv[1].to_lowercase() == "--recursive";
if dir_recursive && argc < 3
{
println!("Usage: {0} [-r|--recursive] <file1|dir1> <file2|dir2> ...", argv[0]);
return;
}
// Enumerate
for item in argv.iter().skip(if dir_recursive {2} else {1})
{
let md = fs::metadata(item);
if md.is_ok()
{
let metadata: Metadata = md.unwrap();
if metadata.is_dir()
{
// Scan directories, but ensure the last
// character is a string separator
if item.chars().last().unwrap() == MAIN_SEPARATOR
{
scan_dir(item, dir_recursive);
}
else
{
let folderpath: String = format!("{0}{1}", item, MAIN_SEPARATOR);
scan_dir(&folderpath, dir_recursive);
}
}
else
{
// Pass file
print_file_status(item);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment