mirror of
https://github.com/lifegpc/libtlg-rs.git
synced 2026-07-08 01:33:22 +08:00
Add TLG5 decode support
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
339
Cargo.lock
generated
Normal file
339
Cargo.lock
generated
Normal 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
3
Cargo.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = ["libtlg-rs", "tlg"]
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal 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.
|
||||||
9
libtlg-rs/Cargo.toml
Normal file
9
libtlg-rs/Cargo.toml
Normal 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
31
libtlg-rs/src/lib.rs
Normal 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
252
libtlg-rs/src/load_tlg.rs
Normal 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
20
libtlg-rs/src/stream.rs
Normal 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
122
libtlg-rs/src/tvpgl.rs
Normal 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
79
libtlg-rs/src/types.rs
Normal 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
11
tlg/Cargo.toml
Normal 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
17
tlg/src/arg.rs
Normal 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
54
tlg/src/main.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user