File size: 6,040 Bytes
9d55850
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f22f7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d55850
 
 
 
 
 
1d3de83
 
 
 
 
 
05dc1e9
 
1d3de83
 
 
 
 
 
7795724
 
 
 
 
1d3de83
 
 
7795724
 
 
1d3de83
 
 
2c6f096
 
5f22f7f
1d3de83
 
 
 
 
 
5f22f7f
 
 
1d3de83
 
 
5f22f7f
1d3de83
 
 
 
05dc1e9
 
 
 
 
 
 
 
 
 
 
 
 
9d55850
 
 
 
 
 
 
 
 
 
 
 
1d3de83
 
 
9d55850
 
 
 
 
 
05dc1e9
 
 
 
 
 
 
 
 
 
 
 
7795724
1d3de83
 
 
7795724
 
 
1d3de83
 
 
 
 
7795724
1d3de83
 
 
2c6f096
1d3de83
 
 
 
5f22f7f
 
 
 
 
 
 
 
1d3de83
 
 
5f22f7f
 
 
 
1d3de83
 
 
9d55850
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d3de83
9d55850
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use serde::{Deserialize, Serialize};
use std::io::{self, Read, Write};

// --- Pointer classification ---

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PointerKind {
    Vtable,
    Code,
    Data,
    Object,
    Heap,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PointerInfo {
    pub offset: u32,
    pub raw_value: u32,
    pub ghidra_value: u32,
    pub kind: PointerKind,
    pub detail: Option<String>,
}

// --- Typed inspection types ---

/// A formatted field value from a struct inspection.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldValue {
    pub offset: u32,
    pub name: String,
    pub size: u32,
    /// Raw hex representation.
    pub hex: String,
    /// Human-readable formatted value.
    pub display: String,
}

/// A tracked live object in the DLL.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiveObjectInfo {
    pub runtime_addr: u32,
    pub ghidra_addr: u32,
    pub size: u32,
    pub class_name: String,
    pub field_count: u32,
}

/// Result of resolving a named alias (e.g., "ddgame") to an address.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedAlias {
    pub runtime_addr: u32,
    pub class_name: String,
}

/// Result of resolving a field name to its offset.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedField {
    pub offset: u32,
    pub size: u32,
}

// --- Protocol messages ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Request {
    Ping,
    Help,
    Read {
        addr: u32,
        len: u32,
        #[serde(default)]
        absolute: bool,
    },
    /// Walk a pointer chain: start at `addr`, then for each offset in `chain`,
    /// deref the current DWORD and add the offset. Read `len` bytes at the end.
    ReadChain {
        addr: u32,
        chain: Vec<u32>,
        len: u32,
        absolute: bool,
    },
    /// Pause the game at the next frame boundary.
    Suspend,
    /// Resume the game.
    Resume,
    /// Advance `count` frames, then pause.
    Step {
        count: i32,
    },
    /// Query current frame number and pause state.
    Frame,
    /// Set a frame breakpoint (-1 to clear).
    Break {
        frame: i32,
    },
    /// Capture a canonicalized game state snapshot.
    Snapshot,
    /// Typed struct inspection: read all named fields at an address.
    Inspect {
        class_name: String,
        addr: u32,
        chain: Vec<u32>,
        absolute: bool,
    },
    /// List all tracked live objects.
    ListObjects,
    /// Resolve a named alias (e.g., "ddgame") to a runtime address.
    ResolveAlias {
        name: String,
    },
    /// Resolve a field name to its offset within a struct.
    ResolveField {
        class_name: String,
        field_name: String,
    },
}

/// One step in a resolved pointer chain, for display.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainStep {
    /// Address we read from
    pub deref_addr: u32,
    /// Value (DWORD) we read
    pub value: u32,
    /// Offset added after deref
    pub offset: u32,
    /// Resulting address (value + offset)
    pub result_addr: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandHelp {
    pub name: String,
    pub usage: String,
    pub description: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Response {
    Pong,
    Help {
        commands: Vec<CommandHelp>,
    },
    ReadResult {
        ghidra_addr: u32,
        runtime_addr: u32,
        data: Vec<u8>,
        pointers: Vec<PointerInfo>,
    },
    ReadChainResult {
        /// Each deref step in the chain (for trace output)
        steps: Vec<ChainStep>,
        /// Final address (Ghidra VA)
        ghidra_addr: u32,
        /// Final address (runtime)
        runtime_addr: u32,
        /// Memory at the final address
        data: Vec<u8>,
        /// Pointer annotations in the final data
        pointers: Vec<PointerInfo>,
    },
    /// Game is now suspended at this frame.
    Suspended {
        frame: i32,
    },
    /// Game resumed.
    Resumed,
    /// Current frame info.
    FrameInfo {
        frame: i32,
        paused: bool,
        breakpoint: i32,
    },
    /// Breakpoint set/cleared.
    BreakSet {
        frame: i32,
    },
    /// Game state snapshot.
    Snapshot {
        frame: i32,
        text: String,
    },
    /// Typed struct inspection result.
    InspectResult {
        class_name: String,
        ghidra_addr: u32,
        runtime_addr: u32,
        fields: Vec<FieldValue>,
    },
    /// List of tracked live objects.
    ObjectList {
        objects: Vec<LiveObjectInfo>,
    },
    /// Resolved named alias.
    AliasResult(ResolvedAlias),
    /// Resolved field offset.
    FieldResult(ResolvedField),
    Error {
        message: String,
    },
}

// --- Length-prefixed framing ---

pub const DEFAULT_PORT: u16 = 19840;
pub const MAX_READ_SIZE: u32 = 1024 * 1024; // 1 MB
pub const MAX_FRAME_SIZE: usize = 8 * 1024 * 1024; // 8 MB (read data + pointer metadata overhead)

/// Write a length-prefixed MessagePack frame.
pub fn write_frame<W: Write, T: Serialize>(writer: &mut W, msg: &T) -> io::Result<()> {
    let payload =
        rmp_serde::to_vec(msg).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
    let len = payload.len() as u32;
    writer.write_all(&len.to_le_bytes())?;
    writer.write_all(&payload)?;
    writer.flush()
}

/// Read a length-prefixed MessagePack frame.
pub fn read_frame<R: Read, T: for<'de> Deserialize<'de>>(reader: &mut R) -> io::Result<T> {
    let mut len_buf = [0u8; 4];
    reader.read_exact(&mut len_buf)?;
    let len = u32::from_le_bytes(len_buf) as usize;
    if len > MAX_FRAME_SIZE {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "frame too large",
        ));
    }
    let mut payload = vec![0u8; len];
    reader.read_exact(&mut payload)?;
    rmp_serde::from_slice(&payload).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}