Add TLG5 decode support

This commit is contained in:
2025-07-02 19:28:54 +08:00
commit 937f8b07b4
14 changed files with 961 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target/

339
Cargo.lock generated Normal file
View File

@@ -0,0 +1,339 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "flate2"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libtlg-rs"
version = "0.1.0"
dependencies = [
"overf",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "overf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63f45a6333db8b6985d6648e4f6c7f2aa814c660c0855c6f58ff67fea8b9f24b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tlg"
version = "0.1.0"
dependencies = [
"clap",
"libtlg-rs",
"png",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

3
Cargo.toml Normal file
View File

@@ -0,0 +1,3 @@
[workspace]
resolver = "3"
members = ["libtlg-rs", "tlg"]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 lifegpc
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.

2
README.md Normal file
View File

@@ -0,0 +1,2 @@
# libtlg-rs
Rust version of libtlg.

9
libtlg-rs/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "libtlg-rs"
version = "0.1.0"
description = "Rust version of libtlg"
edition = "2024"
license = "MIT"
[dependencies]
overf = "0.1"

31
libtlg-rs/src/lib.rs Normal file
View File

@@ -0,0 +1,31 @@
//! A Rust library for processing TLG files.
mod load_tlg;
mod stream;
mod tvpgl;
mod types;
use std::io::{Read, Seek};
pub use types::{Tlg, TlgColorType, TlgError};
/// The result type for TLG operations.
pub type Result<T> = std::result::Result<T, TlgError>;
pub use load_tlg::load_tlg;
/// Check if it's a valid TLG.
///
/// 11 bytes are needed.
pub fn is_valid_tlg(data: &[u8]) -> bool {
if data.len() < 11 {
return false;
}
data == b"TLG0.0\x00sds\x1a" || data == b"TLG5.0\x00raw\x1a" || data == b"TLG6.0\x00raw\x1a"
}
/// Check if it's a valid TLG.
///
/// Same as [`is_valid_tlg`]
pub fn check_tlg<T: Read + Seek>(mut data: T) -> Result<bool> {
let mut header = [0; 11];
data.rewind()?;
data.read_exact(&mut header)?;
Ok(is_valid_tlg(&header))
}

252
libtlg-rs/src/load_tlg.rs Normal file
View File

@@ -0,0 +1,252 @@
use crate::stream::ReadExt;
use crate::tvpgl::*;
use crate::*;
use overf::wrapping;
use std::io::SeekFrom;
fn load_tlg5<T: Read + Seek>(src: &mut T) -> Result<Tlg> {
let colors = src.read_u8()?;
let width = src.read_u32()?;
let height = src.read_u32()?;
let blockheight = src.read_u32()?;
let color = match colors {
3 => TlgColorType::Bgr24,
4 => TlgColorType::Bgra32,
_ => return Err(TlgError::UnsupportedColorType(colors)),
};
let blockcount = ((height - 1) / blockheight) + 1;
src.seek_relative(blockcount as i64 * 4)?;
let stride = width as usize * colors as usize;
let mut output_data = vec![0u8; width as usize * height as usize * colors as usize];
let mut text = [0u8; 4096];
let mut inbuf = vec![0u8; blockheight as usize * width as usize + 10];
let mut outbuf = vec![vec![0u8; blockheight as usize * width as usize + 10]; colors as usize];
let mut prevline: Option<Vec<u8>> = None;
let mut r = 0;
for y_blk in (0..height).step_by(blockheight as usize) {
for c in 0..colors {
let mark = src.read_u8()?;
let size = src.read_u32()?;
if mark == 0 {
src.read_exact(&mut inbuf[..size as usize])?;
r = tlg5_decompress_slide(
&mut outbuf[c as usize],
&inbuf[..size as usize],
size as usize,
&mut text,
r,
);
} else {
src.read_exact(&mut outbuf[c as usize][..size as usize])?;
}
}
let y_lim = (y_blk + blockheight).min(height);
let mut outbufp = Vec::new();
for c in 0..colors {
outbufp.push(outbuf[c as usize].as_slice());
}
for y in y_blk..y_lim {
let current = &mut output_data[(y as usize * stride)..(y as usize * stride + stride)];
match prevline.take() {
Some(prev) => match color {
TlgColorType::Bgr24 => {
tlg5_compose_colors3(current, &prev, &outbufp, width);
outbufp[0] = &outbufp[0][width as usize..];
outbufp[1] = &outbufp[1][width as usize..];
outbufp[2] = &outbufp[2][width as usize..];
}
TlgColorType::Bgra32 => {
tlg5_compose_colors4(current, &prev, &outbufp, width);
outbufp[0] = &outbufp[0][width as usize..];
outbufp[1] = &outbufp[1][width as usize..];
outbufp[2] = &outbufp[2][width as usize..];
outbufp[3] = &outbufp[3][width as usize..];
}
_ => {}
},
None => match color {
TlgColorType::Bgra32 => {
let mut current_pos = 0usize;
let mut pr = 0u8;
let mut pg = 0u8;
let mut pb = 0u8;
let mut pa = 0u8;
for x in 0..width as usize {
let mut b = outbufp[0][x];
let g = outbufp[1][x];
let mut r = outbufp[2][x];
let a = outbufp[3][x];
wrapping! {
b += g;
r += g;
pb += b;
pg += g;
pr += r;
pa += a;
}
current[current_pos] = pb;
current_pos += 1;
current[current_pos] = pg;
current_pos += 1;
current[current_pos] = pr;
current_pos += 1;
current[current_pos] = pa;
current_pos += 1;
}
outbufp[0] = &outbufp[0][width as usize..];
outbufp[1] = &outbufp[1][width as usize..];
outbufp[2] = &outbufp[2][width as usize..];
outbufp[3] = &outbufp[3][width as usize..];
}
TlgColorType::Bgr24 => {
let mut current_pos = 0usize;
let mut pr = 0u8;
let mut pg = 0u8;
let mut pb = 0u8;
for x in 0..width as usize {
let mut b = outbufp[0][x];
let g = outbufp[1][x];
let mut r = outbufp[2][x];
wrapping! {
b += g;
r += g;
pb += b;
pg += g;
pr += r;
}
current[current_pos] = pb;
current_pos += 1;
current[current_pos] = pg;
current_pos += 1;
current[current_pos] = pr;
current_pos += 1;
}
outbufp[0] = &outbufp[0][width as usize..];
outbufp[1] = &outbufp[1][width as usize..];
outbufp[2] = &outbufp[2][width as usize..];
}
_ => {}
},
}
prevline = Some(current.to_vec());
}
}
Ok(Tlg {
tags: Default::default(),
version: 5,
width,
height,
color,
data: output_data,
})
}
fn load_tlg6<T: Read + Seek>(_src: &mut T) -> Result<Tlg> {
Err(TlgError::Str("TLG6 is not supported yet".to_string()))
}
fn internal_load_tlg<T: Read + Seek>(src: &mut T) -> Result<Tlg> {
let mut mark = [0; 11];
src.read_exact(&mut mark)?;
if &mark == b"TLG5.0\x00raw\x1a" {
load_tlg5(src)
} else if &mark == b"TLG6.0\x00raw\x1a" {
load_tlg6(src)
} else {
Err(TlgError::InvalidFormat)
}
}
/// Decode TLG image
pub fn load_tlg<T: Read + Seek>(mut src: T) -> Result<Tlg> {
src.rewind()?;
let mut mark = [0; 11];
src.read_exact(&mut mark)?;
if &mark == b"TLG0.0\x00sds\x1a" {
let rawlen = src.read_u32()?;
let mut tlg = internal_load_tlg(&mut src)?;
let newlen = rawlen as u64 + 15;
src.seek(SeekFrom::Start(newlen))?;
let mut check = true;
while check {
let mut chunkname = [0; 4];
if src.read(&mut chunkname)? != 4 {
break;
}
let chunksize = src.read_u32()?;
if &chunkname == b"tags" {
let mut tag = vec![0; chunksize as usize];
src.read_exact(&mut tag)?;
let mut i = 0;
let len = tag.len();
while i < len {
let mut namelen = 0usize;
let mut c = tag[i];
let mut ok = true;
while c >= b'0' && c <= b'9' {
namelen = namelen * 10 + (c - b'0') as usize;
i += 1;
if i >= len {
ok = false;
break;
}
c = tag[i];
}
if !ok {
break;
}
if c != b':' {
check = false;
break;
}
i += 1;
let name = tag[i..i + namelen].to_vec();
i += namelen;
if i >= len {
break;
}
let mut valuelen = 0usize;
c = tag[i];
ok = true;
while c >= b'0' && c <= b'9' {
valuelen = valuelen * 10 + (c - b'0') as usize;
i += 1;
if i >= len {
ok = false;
break;
}
c = tag[i];
}
if !ok {
break;
}
if c != b':' {
check = false;
break;
}
i += 1;
let value = tag[i..i + valuelen].to_vec();
i += valuelen;
if i >= len {
check = false;
break;
}
c = tag[i];
if c != b',' {
check = false;
break;
}
i += 1;
tlg.tags.insert(name, value);
}
} else {
// skip the chunk
src.seek_relative(chunksize as i64)?;
}
}
Ok(tlg)
} else {
src.rewind()?;
internal_load_tlg(&mut src)
}
}

20
libtlg-rs/src/stream.rs Normal file
View File

@@ -0,0 +1,20 @@
use std::io::Read;
pub trait ReadExt {
fn read_u32(&mut self) -> std::io::Result<u32>;
fn read_u8(&mut self) -> std::io::Result<u8>;
}
impl<R: Read> ReadExt for R {
fn read_u32(&mut self) -> std::io::Result<u32> {
let mut buf = [0; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u8(&mut self) -> std::io::Result<u8> {
let mut buf = [0; 1];
self.read_exact(&mut buf)?;
Ok(buf[0])
}
}

122
libtlg-rs/src/tvpgl.rs Normal file
View File

@@ -0,0 +1,122 @@
use overf::wrapping;
pub fn tlg5_compose_colors3(outp: &mut [u8], upper: &[u8], buf: &[&[u8]], width: u32) {
let mut outpos = 0usize;
let mut upper_pos = 0usize;
let mut pr = 0u8;
let mut pg = 0u8;
let mut pb = 0u8;
for x in 0..width as usize {
let mut b = buf[0][x];
let g = buf[1][x];
let mut r = buf[2][x];
wrapping! {
b += g;
r += g;
}
wrapping! {
pb += b;
pg += g;
pr += r;
}
outp[outpos] = wrapping! { pb + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
outp[outpos] = wrapping! { pg + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
outp[outpos] = wrapping! { pr + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
}
}
pub fn tlg5_compose_colors4(outp: &mut [u8], upper: &[u8], buf: &[&[u8]], width: u32) {
let mut outpos = 0usize;
let mut upper_pos = 0usize;
let mut pr = 0u8;
let mut pg = 0u8;
let mut pb = 0u8;
let mut pa = 0u8;
for x in 0..width as usize {
let mut b = buf[0][x];
let g = buf[1][x];
let mut r = buf[2][x];
let a = buf[3][x];
wrapping! {
b += g;
r += g;
}
wrapping! {
pb += b;
pg += g;
pr += r;
pa += a;
}
outp[outpos] = wrapping! { pb + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
outp[outpos] = wrapping! { pg + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
outp[outpos] = wrapping! { pr + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
outp[outpos] = wrapping! { pa + upper[upper_pos]};
outpos += 1;
upper_pos += 1;
}
}
pub fn tlg5_decompress_slide(
out: &mut [u8],
inp: &[u8],
insize: usize,
text: &mut [u8],
mut r: usize,
) -> usize {
let mut flags = 0u32;
let mut inpos = 0usize;
let mut outpos = 0usize;
while inpos < insize {
wrapping! { flags >>= 1 };
if flags & 256 == 0 {
flags = inp[inpos] as u32 | 0xff00;
inpos += 1;
}
if flags & 1 != 0 {
let mut mpos =
wrapping! { inp[inpos] as usize | ((inp[inpos + 1] as usize & 0xf) << 8) };
let mut mlen = wrapping! { (inp[inpos + 1] as usize & 0xf0) >> 4 };
inpos += 2;
mlen += 3;
if mlen == 18 {
mlen += inp[inpos] as usize;
inpos += 1;
}
while mlen > 0 {
out[outpos] = text[mpos];
outpos += 1;
text[r] = text[mpos];
r += 1;
mpos += 1;
wrapping! {
mpos &= 4095;
r &= 4095;
}
mlen -= 1;
}
} else {
let c = inp[inpos];
inpos += 1;
out[outpos] = c;
outpos += 1;
text[r] = c;
r += 1;
wrapping! {
r &= 4095;
}
}
}
r
}

79
libtlg-rs/src/types.rs Normal file
View File

@@ -0,0 +1,79 @@
use std::{collections::HashMap, hash::Hash};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
/// TLG Color Type
pub enum TlgColorType {
/// Grayscale 8-bit
Grayscale8,
/// BGR 8-bit
Bgr24,
/// BGRA 8-bit
Bgra32,
}
#[derive(Debug, Clone)]
/// TLG Image
pub struct Tlg {
/// Tag dictionary
pub tags: HashMap<Vec<u8>, Vec<u8>>,
/// TLG Version: 0=unknown, 5=v5, 6=v6
pub version: u32,
/// Image width
pub width: u32,
/// Image height
pub height: u32,
/// Color type
pub color: TlgColorType,
/// Image data
pub data: Vec<u8>,
}
#[derive(Debug)]
/// TLG Error
pub enum TlgError {
/// IO Error
Io(std::io::Error),
/// Invalid TLG format
InvalidFormat,
/// Unsupported color type
UnsupportedColorType(u8),
/// String type error
Str(String),
}
impl std::fmt::Display for TlgError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TlgError::Io(e) => write!(f, "IO Error: {}", e),
TlgError::InvalidFormat => write!(f, "Invalid TLG format"),
TlgError::UnsupportedColorType(c) => write!(f, "Unsupported color type: {}", c),
TlgError::Str(s) => write!(f, "{}", s),
}
}
}
impl From<std::io::Error> for TlgError {
fn from(err: std::io::Error) -> Self {
TlgError::Io(err)
}
}
impl From<String> for TlgError {
fn from(err: String) -> Self {
TlgError::Str(err)
}
}
impl From<&str> for TlgError {
fn from(err: &str) -> Self {
TlgError::Str(err.to_string())
}
}
impl From<&String> for TlgError {
fn from(err: &String) -> Self {
TlgError::Str(err.clone())
}
}
impl std::error::Error for TlgError {}

11
tlg/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "tlg"
version = "0.1.0"
description = "Tools to process TLG image file."
edition = "2024"
license = "MIT"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
libtlg-rs = { path = "../libtlg-rs" }
png = "0.17"

17
tlg/src/arg.rs Normal file
View File

@@ -0,0 +1,17 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about)]
/// A command line tool to process TLG files.
pub struct Arg {
/// Path to the input TLG/PNG file.
pub input: String,
/// Path to the output TLG/PNG file.
pub output: Option<String>,
}
impl Arg {
pub fn parse() -> Self {
Parser::parse()
}
}

54
tlg/src/main.rs Normal file
View File

@@ -0,0 +1,54 @@
mod arg;
use std::io::Seek;
fn convert_bgr_to_rgb(data: &mut libtlg_rs::Tlg) {
match data.color {
libtlg_rs::TlgColorType::Bgra32 => {
for i in (0..data.data.len()).step_by(4) {
let b = data.data[i];
data.data[i] = data.data[i + 2];
data.data[i + 2] = b; // Swap red and blue
}
}
libtlg_rs::TlgColorType::Bgr24 => {
for i in (0..data.data.len()).step_by(3) {
let b = data.data[i];
data.data[i] = data.data[i + 2];
data.data[i + 2] = b; // Swap red and blue
}
}
_ => {}
}
}
fn main() {
let args = arg::Arg::parse();
let file = std::fs::File::open(&args.input).expect("Failed to open input file");
let mut file = std::io::BufReader::new(file);
if libtlg_rs::check_tlg(&mut file).expect("Failed to check TLG format") {
let mut tlg = libtlg_rs::load_tlg(&mut file).expect("Failed to load TLG file");
let output = match &args.output {
Some(output) => output.clone(),
None => {
let mut pb = std::path::PathBuf::from(&args.input);
pb.set_extension("png");
pb.to_string_lossy().to_string()
}
};
convert_bgr_to_rgb(&mut tlg);
let mut output_file = std::fs::File::create(&output).expect("Failed to create output file");
let mut encoder = png::Encoder::new(&mut output_file, tlg.width, tlg.height);
encoder.set_color(match tlg.color {
libtlg_rs::TlgColorType::Bgra32 => png::ColorType::Rgba,
libtlg_rs::TlgColorType::Bgr24 => png::ColorType::Rgb,
libtlg_rs::TlgColorType::Grayscale8 => png::ColorType::Grayscale,
});
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().expect("Failed to write PNG header");
writer
.write_image_data(&tlg.data)
.expect("Failed to write PNG image data");
} else {
file.rewind().expect("Failed to rewind file");
}
}