Initial Commit

Will write more useful commit messages in the, the start of this project
was extremely rushed.
This commit is contained in:
2024-04-17 02:01:14 -05:00
commit d0d4ccbd55
11 changed files with 798 additions and 0 deletions

1
.gitignore vendored Normal file
View File

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

39
Cargo.lock generated Normal file
View File

@@ -0,0 +1,39 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "vim_undo_extractor"
version = "0.1.0"
dependencies = [
"anyhow",
"nom",
]

10
Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "vim_undo_extractor"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.82"
nom = "7.1.3"

202
layout.txt Normal file
View File

@@ -0,0 +1,202 @@
START_MAGIC: 56 69 6D 9F 55 6E 44 6F E5 "Vim.UnDoF"
Version: 00 03
SHA-256: A5 B4 88 3B 3A AB FB 50 30 CF 8E 57 58 30 AE 44 38 25 A8 A3 DF B2 BD DF D6 FB E6 F2 EB E8 14 45
Line Count: 00 00 00 7C
Line Length : 00 00 00 16
Line: 20 20 20 20 73 74 64 3A 3A 73 74 72 69 6E 67 20 69 6E 70 75 74 3B " std::string input;"
Line Number: 00 00 00 2B
Column Number: 00 00 00 15
Old Head Sequence or 0 if null: 00 00 00 01
New Head Sequence or 0 if null: 00 00 00 83
Current Head Sequence or 0 if null: 00 00 00 83
Numhead: 00 00 00 83
Sequence Last: 00 00 00 83
Sequence Current: 00 00 00 82
Time: 00 00 00 00 66 10 CE 39
Optional fields
04
01
00 00 00 40
End Marker: 00
Header Magic: 5F D0
Next Pointer: 00 00 00 00
Previous Pointer: 00 00 00 02
Alt Next Pointer: 00 00 00 00
Alt Previous Pointer: 00 00 00 00
Sequence: 00 00 00 01
Position
Line Number: 00 00 00 54
Column: 00 00 00 00
Coladd: 00 00 00 00
Cursor VCol: FF FF FF FF
Flags: 00 01
00 00
Marks
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
Visual Info:
Start Position:
00 00 00 00
00 00 00 00
00 00 00 00
End Position:
00 00 00 00
00 00 00 00
00 00 00 00
Vi Mode: 00 00 00 00
Vi_curswant: 00 00 00 00
Time: 00 00 00 00 66 10 BC DC
Optional Fields:
04
01
00 00 00 00
End Marker: 00
Entry Magic: F5 18
Entry Type: 00 00 00 00
Entry Data: 54 00 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00
Entry Magic: F5 18
Entry Type: 00 00 00 00
Entry Data: 55 00 00 00 04 00 00 00 00 00 00 00 05
00 00 00 00 00 00 00 05 00 00 00 71 06 00 00 00
00 00 00 05 00 00 00 00 00 00 00 05 00 00 00 00
00 00 00
Entry End Magic: 35 81
Header Magic: 5F D0
00 00 00 01 00 00 00 03 00

28
src/main.rs Normal file
View File

@@ -0,0 +1,28 @@
use anyhow::Result;
mod undo;
mod parse;
use undo::UndoFile;
fn main() -> Result<()> {
let undo_file = UndoFile::from_path("./undo_file")?;
for header in undo_file.headers {
for entry in header.entries {
let section: String = entry.section
.iter()
.map(|b| char::from_u32(*b as u32).unwrap_or(' '))
.collect();
println!("{}", section);
}
}
Ok(())
}

42
src/parse.rs Normal file
View File

@@ -0,0 +1,42 @@
use nom::{
bytes::complete::take,
combinator::map_res,
IResult,
error::Error,
};
pub(crate) mod start_header;
pub(crate) mod header;
pub(crate) mod entry;
pub fn bytes_u32(input: &[u8]) -> IResult<&[u8], u32> {
map_res(
take(4usize),
|b: &[u8]| Ok::<u32, Error<&[u8]>>(u32::from_be_bytes(b.try_into().unwrap()))
)(input)
}
fn time(input: &[u8]) -> IResult<&[u8], u64> {
map_res(
take(8usize),
|b: &[u8]| Ok::<u64, Error<&[u8]>>(u64::from_be_bytes(b.try_into().unwrap()))
)(input)
}
#[derive(Debug)]
struct OptionalFields((u8, u8, u32));
fn optional_fields(input: &[u8]) -> IResult<&[u8], OptionalFields> {
map_res(
take(6usize),
|b: &[u8]| {
Ok::<OptionalFields, Error<&[u8]>>(
OptionalFields ((
b[0],
b[1],
u32::from_be_bytes(b[2..6].try_into().unwrap())
))
)
}
)(input)
}

97
src/parse/entry.rs Normal file
View File

@@ -0,0 +1,97 @@
use nom::{
IResult,
Parser,
Or,
error::Error,
combinator::map_res,
sequence::{tuple, pair},
multi::many0,
bytes::complete::{tag, take, take_until},
};
use super::bytes_u32;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Entry {
pub(crate) entry_type: EntryType,
pub(crate) section: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq)]
enum EntryType {
Unknown(u32),
}
fn magic(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\xf5\x18")(input)
}
fn entry(input: &[u8]) -> IResult<&[u8], Entry> {
let (input, section_type) = take(4usize)(input)?;
let (input, section) = take_until(b"\xf5\x18".as_ref())(input)?;
let (input, _) = tag(b"\xf5\x18")(input)?;
let entry_type = match section_type {
num => EntryType::Unknown(u32::from_be_bytes(num.try_into().unwrap())),
};
println!("{:#?}", section);
Ok((
input,
Entry {
entry_type,
section: section.to_vec(),
}
))
}
fn sections(input: &[u8]) -> IResult<&[u8], Vec<Entry>> {
let (input, sections) = take_until(b"\x35\x81".as_ref())(input)?;
let (last_entry, mut entries) = many0(entry)(sections)?;
entries.push(
Entry {
entry_type: match last_entry[0..4] {
_ => EntryType::Unknown(u32::from_be_bytes(last_entry[0..4].try_into().unwrap())),
},
section: last_entry[4..].to_vec(),
});
Ok((input, entries))
}
pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Vec<Entry>> {
let (input, (
_,
entries,
_,
)) = tuple((
magic,
sections,
tag(b"\x35\x81".as_ref())
))(input).unwrap();
Ok((
input,
entries,
))
}
#[cfg(test)]
mod tests {
use super::{sections, Entry, EntryType};
#[test]
fn test_sections() {
let test_str = b"\x00\x00\x00\x00\xaa\xaa\xf5\x18\x00\x00\x00\x00\xaa\xaa\xf5\x18\x00\x00\x00\x00\xaa\xaa\x35\x81";
assert_eq!(sections(test_str).unwrap().1, vec![
Entry {
entry_type: EntryType::Unknown,
section: vec![b'\xaa', b'\xaa'],
},
])
}
}

175
src/parse/header.rs Normal file
View File

@@ -0,0 +1,175 @@
use nom::{
IResult,
error::Error,
combinator::map_res,
sequence::tuple,
bytes::complete::{tag, take},
};
use super::{bytes_u32, time, optional_fields, OptionalFields};
use crate::parse::entry::Entry;
#[derive(Debug)]
pub(crate) struct Header {
pub(crate) next: u32,
pub(crate) previous: u32,
pub(crate) alt_next: u32,
pub(crate) alt_previous: u32,
pub(crate) sequence: u32,
pub(crate) position: Position,
pub(crate) cursor_vcol: u32,
pub(crate) flags: Vec<Flag>,
pub(crate) marks: Vec<u8>,
pub(crate) visual_info: VisualInfo,
pub(crate) time: u64,
pub(crate) optional_fields: OptionalFields,
pub(crate) entries: Vec<Entry>
}
#[derive(Debug)]
struct Position {
line_number: u32,
column_number: u32,
coladd: u32,
}
#[derive(Debug)]
enum Flag {
Unknown,
}
#[derive(Debug)]
struct VisualInfo {
start: Position,
end: Position,
mode: u32,
curswant: u32,
}
fn magic(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\x5f\xd0".as_ref())(input)
}
fn position(input: &[u8]) -> IResult<&[u8], Position> {
map_res(
tuple((
bytes_u32,
bytes_u32,
bytes_u32,
)),
|(line_number, column_number, coladd)| {
Ok::<Position, Error<&[u8]>>(Position {
line_number,
column_number,
coladd,
})
}
)(input)
}
fn visual_info(input: &[u8]) -> IResult<&[u8], VisualInfo> {
map_res(
tuple((
position,
position,
bytes_u32,
bytes_u32,
)),
|(start, end, mode, curswant)| {
Ok::<VisualInfo, Error<&[u8]>>(
VisualInfo {
start,
end,
mode,
curswant,
}
)
}
)(input)
}
fn flags(input: &[u8]) -> IResult<&[u8], Vec<Flag>> {
map_res(
take(4usize),
|flags: &[u8]| {
Ok::<Vec<Flag>, Error<&[u8]>>(
flags.into_iter().map(|b| {
match b {
_ => Flag::Unknown
}
}).collect()
)
}
)(input)
}
fn marks(input: &[u8]) ->IResult<&[u8], &[u8]> {
take(310usize)(input)
}
fn end_marker(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\x00")(input)
}
pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Header> {
let next = bytes_u32;
let previous = bytes_u32;
let alt_next = bytes_u32;
let alt_previous = bytes_u32;
let sequence = bytes_u32;
let cursor_vcol = bytes_u32;
let time = time;
let (input, (
_,
next,
previous,
alt_next,
alt_previous,
sequence,
position,
cursor_vcol,
flags,
marks,
visual_info,
time,
optional_fields,
_,
)) = tuple((
magic,
next,
previous,
alt_next,
alt_previous,
sequence,
position,
cursor_vcol,
flags,
marks,
visual_info,
time,
optional_fields,
end_marker,
))(input).unwrap();
Ok((
input,
Header {
next,
previous,
alt_next,
alt_previous,
sequence,
position,
visual_info,
cursor_vcol,
flags,
marks: marks.to_vec(),
time,
optional_fields,
entries: Vec::new(),
}
))
}

154
src/parse/start_header.rs Normal file
View File

@@ -0,0 +1,154 @@
use nom::{
IResult,
error::Error,
combinator::map_res,
sequence::tuple,
bytes::complete::{tag, take},
};
use super::{bytes_u32, time, optional_fields, OptionalFields};
fn magic(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\x56\x69\x6D\x9F\x55\x6E\x44\x6F\xE5")(input)
}
fn version(input: &[u8]) -> IResult<&[u8], u16> {
map_res(
take(2usize),
|b: &[u8]| Ok::<u16, Error<&[u8]>>(u16::from_be_bytes(b.try_into().unwrap()))
)(input)
}
fn hash(input: &[u8]) -> IResult<&[u8], &[u8; 32]> {
map_res(
take(32usize),
|b: &[u8]| Ok::<&[u8; 32], Error<&[u8]>>(b.try_into().unwrap())
)(input)
}
fn line_with_length(input: &[u8]) -> IResult<&[u8], String> {
let (input, line_length) = bytes_u32(input)?;
map_res(
take(line_length),
|b: &[u8]| Ok::<String, Error<&[u8]>>(String::from_utf8(b.to_vec()).expect("Invalid UTF-8"))
)(input)
}
fn end_marker(input: &[u8]) -> IResult<&[u8], &[u8]> {
tag(b"\x00".as_ref())(input)
}
#[derive(Debug)]
pub(crate) struct StartHeader {
version: u16,
hash: Vec<u8>,
line_count: u32,
line: String,
line_number: u32,
column_number: u32,
old_head_sequence: u32,
new_head_sequence: u32,
current_head_sequence: u32,
numhead: u32,
last_sequence: u32,
current_sequence: u32,
time: u64,
optional_fields: OptionalFields,
}
pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], StartHeader> {
let line_count = bytes_u32;
let line_number = bytes_u32;
let column_number = bytes_u32;
let old_head_sequence = bytes_u32;
let new_head_sequence = bytes_u32;
let current_head_sequence = bytes_u32;
let numhead = bytes_u32;
let last_sequence = bytes_u32;
let current_sequence = bytes_u32;
let (input, (
_,
version,
hash,
line_count,
line,
line_number,
column_number,
old_head_sequence,
new_head_sequence,
current_head_sequence,
numhead,
last_sequence,
current_sequence,
time,
optional_fields,
_,
)) = tuple ((
magic,
version,
hash,
line_count,
line_with_length,
line_number,
column_number,
old_head_sequence,
new_head_sequence,
current_head_sequence,
numhead,
last_sequence,
current_sequence,
time,
optional_fields,
end_marker,
))(&input).unwrap();
Ok(
(
input,
StartHeader {
version,
hash: hash.to_vec(),
line_count,
line,
line_number,
column_number,
old_head_sequence,
new_head_sequence,
current_head_sequence,
numhead,
last_sequence,
current_sequence,
time,
optional_fields,
}
)
)
}
#[cfg(test)]
mod tests {
use super::{magic, version};
#[test]
fn test_magic() {
let test_str = b"\x56\x69\x6d\x9f\x55\x6e\x44\x6f\xe5\x00\x03";
let (_, magic) = magic(test_str).unwrap();
assert_eq!(magic, b"\x56\x69\x6d\x9f\x55\x6e\x44\x6f\xe5");
}
#[test]
fn test_version() {
let test_str = b"\x00\x03\xa5\xb4";
let (_, version) = version(test_str).unwrap();
assert_eq!(version, 3);
}
}

50
src/undo.rs Normal file
View File

@@ -0,0 +1,50 @@
use std::fs::File;
use std::io::Read;
use anyhow::{Result, anyhow};
use crate::parse::start_header::{self, StartHeader};
use crate::parse::header::{self, Header};
use crate::parse::entry::{self, Entry};
use nom::{
sequence::tuple,
multi::many0,
bytes::complete::{take_until, tag},
};
#[derive(Debug)]
pub(crate) struct UndoFile {
pub(crate) start_header: StartHeader,
pub(crate) headers: Vec<Header>,
}
impl UndoFile {
pub(crate) fn from_path(path: &str) -> Result<Self> {
let mut buffer: Vec<u8> = Vec::new();
let mut file = File::open(path)?;
let _ = match file.read_to_end(&mut buffer) {
Ok(_) => Ok(()),
Err(e) => Err(anyhow!(e)),
};
let (input, start_header) = start_header::parse(&buffer).unwrap();
let (_, out) = many0(tuple((header::parse, entry::parse, take_until(b"\x5f\xd0".as_ref()))))(input).unwrap();
let headers = out
.into_iter()
.map(|(mut header, entries, _)| {
entries.into_iter().for_each(|entry| header.entries.push(entry));
header
}).collect();
Ok(
Self {
start_header,
headers,
}
)
}
}

BIN
undo_file Normal file

Binary file not shown.