1use chrono::{TimeZone, Utc};
2use std::io::{self, Write};
3use std::time::Duration;
4use vex_v5_serial::packets::factory::{
5 FactoryEnablePacket, FactoryEnablePayload, FactoryEnableReplyPacket,
6};
7
8use vex_v5_serial::packets::file::ExtensionType;
9use vex_v5_serial::timestamp::J2000_EPOCH;
10use vex_v5_serial::{
11 connection::{serial::SerialConnection, Connection},
12 packets::file::{
13 FileVendor, GetDirectoryEntryPacket, GetDirectoryEntryPayload,
14 GetDirectoryEntryReplyPacket, GetDirectoryFileCountPacket, GetDirectoryFileCountPayload,
15 GetDirectoryFileCountReplyPacket,
16 },
17};
18
19use humansize::{format_size, BINARY};
20use tabwriter::TabWriter;
21
22use crate::errors::CliError;
23
24fn vendor_prefix(vid: FileVendor) -> &'static str {
25 match vid {
26 FileVendor::User => "user/",
27 FileVendor::Sys => "sys_/",
28 FileVendor::Dev1 => "rmsh/",
29 FileVendor::Dev2 => "pros/",
30 FileVendor::Dev3 => "mwrk/",
31 FileVendor::Dev4 => "deva/",
32 FileVendor::Dev5 => "devb/",
33 FileVendor::Dev6 => "devc/",
34 FileVendor::VexVm => "vxvm/",
35 FileVendor::Vex => "vex_/",
36 FileVendor::Undefined => "test/",
37 }
38}
39
40pub async fn dir(connection: &mut SerialConnection) -> Result<(), CliError> {
41 let mut tw = TabWriter::new(io::stdout());
42
43 const USEFUL_VIDS: [FileVendor; 11] = [
44 FileVendor::User,
45 FileVendor::Sys,
46 FileVendor::Dev1,
47 FileVendor::Dev2,
48 FileVendor::Dev3,
49 FileVendor::Dev4,
50 FileVendor::Dev5,
51 FileVendor::Dev6,
52 FileVendor::VexVm,
53 FileVendor::Vex,
54 FileVendor::Undefined,
55 ];
56
57 connection
58 .packet_handshake::<FactoryEnableReplyPacket>(
59 Duration::from_millis(500),
60 1,
61 FactoryEnablePacket::new(FactoryEnablePayload::new()),
62 )
63 .await
64 .unwrap();
65
66 write!(
67 &mut tw,
68 "\x1B[1mName\tSize\tLoad Address\tVendor\tType\tTimestamp\tVersion\tCRC32\n\x1B[0m"
69 )
70 .unwrap();
71 for vid in USEFUL_VIDS {
72 let file_count = connection
73 .packet_handshake::<GetDirectoryFileCountReplyPacket>(
74 Duration::from_millis(500),
75 1,
76 GetDirectoryFileCountPacket::new(GetDirectoryFileCountPayload {
77 vendor: vid,
78 option: 0,
79 }),
80 )
81 .await?;
82
83 for n in 0..file_count.payload {
84 if let Some(entry) = connection
85 .packet_handshake::<GetDirectoryEntryReplyPacket>(
86 Duration::from_millis(500),
87 1,
88 GetDirectoryEntryPacket::new(GetDirectoryEntryPayload {
89 file_index: n as u8,
90 unknown: 0,
91 }),
92 )
93 .await?
94 .payload
95 {
96 writeln!(
97 &mut tw,
98 "{}{}\t{}\t{}\t{:?}\t{}\t{}\t{}\t{}",
99 vendor_prefix(vid),
100 entry.file_name,
101 format_size(entry.size, BINARY),
102 if entry.load_address == u32::MAX {
103 "-".to_string()
104 } else {
105 format!("{:#x}", entry.load_address)
106 },
107 vid,
108 entry
109 .metadata
110 .as_ref()
111 .map(|m| match m.extension_type {
112 ExtensionType::Binary => "binary",
113 ExtensionType::EncryptedBinary => "encrypted",
114 ExtensionType::Vm => "vm",
115 })
116 .unwrap_or("system"),
117 entry
118 .metadata
119 .as_ref()
120 .map(|m| Utc
121 .timestamp_millis_opt((J2000_EPOCH as i64 + m.timestamp as i64) * 1000)
122 .unwrap()
123 .format("%Y-%m-%d %H:%M:%S")
124 .to_string())
125 .unwrap_or("-".to_string()),
126 entry
127 .metadata
128 .as_ref()
129 .map(|m| format!(
130 "{}.{}.{}.b{}",
131 m.version.major, m.version.minor, m.version.build, m.version.beta
132 ))
133 .unwrap_or("-".to_string()),
134 if entry.crc == u32::MAX {
135 "-".to_string()
136 } else {
137 format!("{:#x}", entry.crc)
138 },
139 )
140 .unwrap();
141 }
142 }
143 }
144
145 tw.flush().unwrap();
146
147 Ok(())
148}