- Introduced a comprehensive markdown document outlining the API surface of RSNet.dll related to downloading video files. - Documented initialization, connection, record querying, and download processes. - Provided insights into the exported functions, their parameters, and expected behaviors. - Included practical implications and recommendations for implementing a downloader script using C# interop. - Highlighted the necessary struct layouts and callback mechanisms for effective integration with the DLL.
911 lines
No EOL
29 KiB
Markdown
911 lines
No EOL
29 KiB
Markdown
# RSNet Download Analysis
|
|
|
|
Date: 2026-04-17
|
|
|
|
Target binary: `RSNet.dll`
|
|
|
|
Related binaries:
|
|
|
|
- `Surveillance_client.exe`
|
|
- `RSPlay.dll`
|
|
|
|
Analysis method: Ghidra MCP against the loaded `RSNet.dll`, using read-only inspection plus function renames/comments only.
|
|
|
|
## Objective
|
|
|
|
Determine enough of the `RSNet.dll` API and behavior to build a simple script that can:
|
|
|
|
1. initialize the DLL
|
|
2. connect/login to a device
|
|
3. query recorded segments for a camera
|
|
4. request download of a chosen interval or file segment
|
|
5. wait for completion and retrieve the saved file
|
|
|
|
## Bottom Line
|
|
|
|
At this point, the DLL has been analyzed far enough to outline a realistic first-pass downloader script that calls `RSNet.dll` directly with `ctypes` or similar FFI.
|
|
|
|
What is established:
|
|
|
|
- `RSNet.dll` owns the real connection, login, record-query, playback, and record-download logic.
|
|
- `RSNet.dll` performs its own socket I/O and background worker management.
|
|
- the EXE is mainly a UI and adapter layer that fills structs and hands them to `RSNet.dll`
|
|
- the download worker inside `RSNet.dll` writes data locally through an internal record-file layer rather than requiring the caller to manually process frame packets
|
|
- a simple standalone script is plausible without reversing the entire wire protocol, because the DLL already encapsulates the transport
|
|
|
|
What is not fully nailed down yet:
|
|
|
|
- exact C struct definitions for all exported APIs
|
|
- the full callback signature set and all event codes
|
|
- exact output container selection logic between AVI, MP4, or the vendor private format
|
|
|
|
Even with those gaps, there is now enough to define the likely call sequence and the minimum reverse-engineering targets for a first prototype.
|
|
|
|
## Exported API Surface Relevant To Downloading
|
|
|
|
Confirmed exports in `RSNet.dll`:
|
|
|
|
- `RSNetInit` at `0x1001ae30`
|
|
- `RSNetSetEncription` at `0x1001b570`
|
|
- `RSNetConnectionStart` at `0x1001c350`
|
|
- `RSNetConnectionStartEx` at `0x1001c3a0`
|
|
- `RSNetQueryRecord` at `0x1001c640`
|
|
- `RSNetAsyncQueryRecord` at `0x1001c660`
|
|
- `RSNetQueryRecordEx` at `0x1001c680`
|
|
- `RSNetAsyncQueryRecordEx` at `0x1001c6a0`
|
|
- `RSNetAsyncQueryRecordStop` at `0x1001c6c0`
|
|
- `RSNetStartDownloadRecord` at `0x1001c780`
|
|
- `RSNetStartDownloadRecordEx` at `0x1001c810`
|
|
- `RSNetStartRecordPlay` at `0x1001c870`
|
|
- `RSNetStartRecordPlayEx` at `0x1001c9a0`
|
|
- `RSNetReqRecordData` at `0x1001ca00`
|
|
- `RSNetReqRecordPlayCtrol` at `0x1001ca40`
|
|
- `RSNetRelease` at `0x1001b4e0`
|
|
|
|
Interpretation:
|
|
|
|
- a standalone downloader should use the DLL exports directly
|
|
- there is no need to reproduce the underlying socket protocol if the goal is just to automate download on Windows where the vendor DLL is available
|
|
|
|
## Initialization
|
|
|
|
### `RSNetInit`
|
|
|
|
`RSNetInit`:
|
|
|
|
- initializes logging from `RSNet.ini`
|
|
- calls `WSAStartup(0x202, ...)`
|
|
- prepares global DLL state
|
|
|
|
Practical implication:
|
|
|
|
- any standalone script must call `RSNetInit()` before any connection or query function
|
|
|
|
### `RSNetSetEncription`
|
|
|
|
`RSNetSetEncription(value)` simply stores a global value.
|
|
|
|
Practical implication:
|
|
|
|
- there is some configurable encryption mode or toggle
|
|
- it may need to be set explicitly for some device families, but this has not yet been proven necessary for the basic flow
|
|
|
|
## Connection / Login
|
|
|
|
### Public entry points
|
|
|
|
- `RSNetConnectionStart`
|
|
- `RSNetConnectionStartEx`
|
|
|
|
Both functions:
|
|
|
|
- allocate a per-device/session object of size `0xe98`
|
|
- initialize internal locks, events, strings, and worker state
|
|
- copy caller-provided login parameters into the session object
|
|
- queue a background login/connection worker
|
|
- return a session handle pointer on success, or `NULL` on failure
|
|
|
|
### `RSNetConnectionStart`
|
|
|
|
This is the simpler path and likely the better first target for a prototype.
|
|
|
|
From decompilation, the input struct used by `RSNetConnectionStart` appears to have this rough layout:
|
|
|
|
```c
|
|
struct RSNET_CONNECT_REQ_BASE {
|
|
char *address_or_id; // offset 0x00
|
|
uint16_t port; // offset 0x04
|
|
char *username; // offset 0x08
|
|
char *password; // offset 0x0c
|
|
uint32_t field10; // offset 0x10
|
|
uint32_t field14; // offset 0x14
|
|
uint32_t field18; // offset 0x18
|
|
uint32_t field1c; // offset 0x1c
|
|
uint32_t field20; // offset 0x20
|
|
uint32_t field24; // offset 0x24
|
|
uint32_t field28; // offset 0x28
|
|
};
|
|
```
|
|
|
|
The DLL copies these into its internal session object as:
|
|
|
|
- destination string at session `+0x04`
|
|
- port at session `+0x20`
|
|
- username at session `+0x28`
|
|
- password at session `+0x44`
|
|
- several mode or callback related fields at `+0x68 .. +0x80`
|
|
|
|
### `RSNetConnectionStartEx`
|
|
|
|
The extended path supports more than direct IP login. Decompiled behavior shows:
|
|
|
|
- a connection mode field at request index `0x0b`
|
|
- if that mode is zero, the DLL treats the first string as the device address directly
|
|
- if non-zero, it stores the first string in an alternate field and forces the target address to `127.0.0.1`
|
|
- when mode is `1` or `10`, it copies an additional string from request index `0x0c`
|
|
|
|
Interpretation:
|
|
|
|
- `RSNetConnectionStartEx` likely supports P2P / relay / localhost-tunneled connection modes
|
|
- the simple downloader should avoid the `Ex` path initially unless the target system requires P2P rather than direct device access
|
|
|
|
### Login worker behavior
|
|
|
|
The session worker at `0x100074b0`:
|
|
|
|
- attempts login / session setup
|
|
- posts state changes through callback or message mechanisms
|
|
- sends heartbeats every ~5 seconds after login
|
|
- dispatches status notifications with integer codes
|
|
|
|
Observed status/event codes from the worker:
|
|
|
|
- `2` on one failure path
|
|
- `3` on disconnect/connection-lost path
|
|
- `0x65`
|
|
- `0x79`
|
|
- `0x7a`
|
|
|
|
Strings in the DLL map login-related status messages to names such as:
|
|
|
|
- `RSNetMsgLoginRequest`
|
|
- `RSNetMsgLoginSuccess`
|
|
- `RSNetMsgLoginUserLogined`
|
|
- `RSNetMsgLoginNoUserName`
|
|
- `RSNetMsgLoginPasswordError`
|
|
- `RSNetMsgLoginNoRight`
|
|
- `RSNetMsgOverMaxUser`
|
|
- `RSNetMsgLoginUserDisable`
|
|
- `RSNetMsgLoginFail`
|
|
|
|
Practical implication:
|
|
|
|
- a script should assume login is asynchronous
|
|
- a callback or polling/wait mechanism is required before issuing record queries
|
|
|
|
## Record Query
|
|
|
|
### Public entry points
|
|
|
|
- `RSNetQueryRecord`
|
|
- `RSNetQueryRecordEx`
|
|
|
|
The simpler function is:
|
|
|
|
```c
|
|
RSNetQueryRecord(session, query_req)
|
|
```
|
|
|
|
which routes into `FUN_10005ca0`.
|
|
|
|
The extended function is:
|
|
|
|
```c
|
|
RSNetQueryRecordEx(session, query_req)
|
|
```
|
|
|
|
which routes into `FUN_10005eb0`.
|
|
|
|
### Behavior
|
|
|
|
Both paths:
|
|
|
|
- build a request message with message subtype `0x6f`
|
|
- send it through the session object
|
|
- wait up to 20 seconds for a response
|
|
- decode the returned payload into caller-facing record structures through callback helpers
|
|
|
|
The record-query dispatcher distinguishes at least two output styles using the first field of the query struct:
|
|
|
|
- `0x65`
|
|
- `0x66`
|
|
|
|
Those two cases use different callbacks:
|
|
|
|
- `FUN_100046b0`
|
|
- `FUN_10004750`
|
|
|
|
`FUN_100046b0` iterates a result array in units of `0x1c` bytes.
|
|
|
|
`FUN_10004750` iterates a result array in units of `0x0c` bytes.
|
|
|
|
Interpretation:
|
|
|
|
- one query result format is likely the full record segment list (`0x1c` bytes each)
|
|
- the other is likely a compact per-day/per-file summary format (`0x0c` bytes each)
|
|
|
|
For a downloader, the `0x1c` record entries are the important one because the EXE uses `0x1c`-byte records when building playback and download requests.
|
|
|
|
### Likely record entry format
|
|
|
|
The download worker consumes entries of size `0x1c` and treats them like timestamped record descriptors.
|
|
|
|
From earlier EXE analysis and DLL behavior, each `0x1c` entry likely contains:
|
|
|
|
- start date/time fields
|
|
- end date/time fields
|
|
- channel or stream selection data
|
|
- a record type flag or subtype
|
|
|
|
This matches how the EXE performed time-range intersection over returned recording segments.
|
|
|
|
## Download
|
|
|
|
### Public entry points
|
|
|
|
- `RSNetStartDownloadRecord`
|
|
- `RSNetStartDownloadRecordEx`
|
|
|
|
These are the core APIs for a standalone downloader.
|
|
|
|
### `RSNetStartDownloadRecord`
|
|
|
|
This is the simpler form.
|
|
|
|
The EXE-side wrapper showed this request shape:
|
|
|
|
```c
|
|
struct RSNET_DOWNLOAD_REQ_BASE {
|
|
uint32_t field00;
|
|
uint32_t field04;
|
|
uint32_t field08;
|
|
uint32_t callback_or_ctx;
|
|
uint32_t callback_user;
|
|
};
|
|
```
|
|
|
|
Inside `RSNet.dll`, `FUN_10010330` adapts this into the extended worker request by supplying defaults:
|
|
|
|
- count or mode fields are normalized
|
|
- an extra byte field is forced to zero
|
|
- a `local_14 = 1` flag is inserted before forwarding to the common worker builder
|
|
|
|
Interpretation:
|
|
|
|
- the base function is just a convenience wrapper around the extended worker
|
|
- for a script, `RSNetStartDownloadRecord` is probably easier to prototype first
|
|
|
|
### `RSNetStartDownloadRecordEx`
|
|
|
|
This is the common implementation target.
|
|
|
|
The decompiled request consumer `FUN_100103b0` proves the worker expects at least:
|
|
|
|
- two required non-zero fields at offsets `+0x04` and `+0x08`
|
|
- a count field at `+0x0c`
|
|
- an extra mode byte at `+0x10`
|
|
- a sequence of seven 32-bit values copied from offsets `+0x00 .. +0x18` into the worker object
|
|
|
|
Then it:
|
|
|
|
- allocates `count * 0x1c` bytes for one array
|
|
- allocates `count * 0x108` bytes for a second array
|
|
- copies source arrays from worker request indices `0x19` and `0x1a`
|
|
- queues a background worker thread beginning at `0x100105b0`
|
|
|
|
Interpretation:
|
|
|
|
- the extended request supports batch download of one or more `0x1c` record descriptors
|
|
- each record also has an associated `0x108` byte metadata block, likely including the destination path or output-file information
|
|
|
|
This is a strong indication that a standalone downloader can submit one selected record entry at a time rather than needing a large complex batch.
|
|
|
|
## Download Worker Behavior
|
|
|
|
The worker at `0x100105b0` is the core of remote file download.
|
|
|
|
Key verified behaviors:
|
|
|
|
- waits on session/login readiness
|
|
- opens a socket and configures timeouts
|
|
- sends a request with message subtype:
|
|
- `0x83` in one mode
|
|
- `0x196` in another mode
|
|
- copies one or more `0x1c` record entries into the outgoing message
|
|
- optionally handles an additional payload block depending on mode
|
|
- receives a response header and payload
|
|
- processes download status and progress messages
|
|
- writes or routes the resulting data into an internal record-file path
|
|
|
|
Important status/message strings in the DLL:
|
|
|
|
- `RSNetMsgDownloadRecordClosed`
|
|
- `RSNetMsgDownloadRecordStoreFail`
|
|
- `RSNetMsgDownloadRecordNoFile`
|
|
- `RSNetMsgDownloadRecordOK`
|
|
- `RSNetMsgDownloadRecordPercent`
|
|
- `RSNetMsgDownloadRecordFail`
|
|
|
|
Observed message codes in the worker:
|
|
|
|
- `0x132`
|
|
- `0x133`
|
|
- `0x142`
|
|
- `0x145`
|
|
|
|
Interpretation:
|
|
|
|
- the worker sends progress and completion back to the caller via callback/message dispatch
|
|
- success/failure is not just the return value of `RSNetStartDownloadRecord*`; the returned handle only means the worker was started
|
|
|
|
## Does The DLL Save The File Itself?
|
|
|
|
Yes, that appears to be the case.
|
|
|
|
Evidence:
|
|
|
|
- the download worker is large and manages long-running transfer state
|
|
- the DLL contains internal record-file classes:
|
|
- `.?AVCBaseRecordFile@@`
|
|
- `.?AVCRSAVIRecordFile@@`
|
|
- `.?AVCRSMP4RecordFile@@`
|
|
- `.?AVCRSPrivateRecordFile@@`
|
|
- the DLL also contains AVI-writing error strings
|
|
- cleanup paths include an object with a `FILE *` at offset `0x54` and a file/path buffer with small-string optimization behavior
|
|
|
|
Practical implication:
|
|
|
|
- the script most likely does not need to manually receive frame payloads and write them itself
|
|
- instead, it should provide the request/path metadata that the DLL expects, start the download, and wait for the worker to finish writing the file
|
|
|
|
## Callback And Handle Lifetime
|
|
|
|
### Session handle
|
|
|
|
The session handle returned by `RSNetConnectionStart*`:
|
|
|
|
- owns socket state
|
|
- runs a worker thread
|
|
- dispatches status via callback or window message
|
|
|
|
Important internal callback-related session fields:
|
|
|
|
- callback window/message at offsets around `+0x68` and `+0x6c`
|
|
- callback function at `+0x78`
|
|
- callback user/context at `+0x70`
|
|
|
|
### Download handle
|
|
|
|
The download handle returned by `RSNetStartDownloadRecord*` is a heap object with:
|
|
|
|
- session pointer at `+0x20`
|
|
- event handle at `+0x2c`
|
|
- worker state, copied record entries, and additional metadata
|
|
|
|
There is a tiny internal stop/cleanup wrapper around `0x1001ca81` that:
|
|
|
|
- validates the handle
|
|
- calls a stop routine
|
|
- destroys the download object via `FUN_1001cab0`
|
|
|
|
So a full script should ideally also locate or call the exported stop-download function when cleanup is needed. The original EXE import table proves that such an export exists, even though the current symbol recovery in this DLL session did not label it cleanly.
|
|
|
|
## Practical Downloader Strategy
|
|
|
|
The simplest viable approach is not to reverse the raw network protocol. It is to call the vendor DLL directly.
|
|
|
|
## C# Interop Feasibility
|
|
|
|
Yes. C# can load `RSNet.dll` as an unmanaged native library and call its exports directly.
|
|
|
|
The DLL is a good fit for `P/Invoke` or `NativeLibrary.GetExport`-based interop because the key exports are ordinary x86 functions, not COM interfaces or C++ object methods.
|
|
|
|
### ABI details confirmed from `RSNet.dll`
|
|
|
|
- architecture: `x86/little/32`
|
|
- pointer size: `4`
|
|
- compiler family: Visual Studio
|
|
- export names are undecorated in the import table used by the EXE
|
|
- the relevant exports use callee stack cleanup, which matches `StdCall`
|
|
|
|
Observed export epilogues:
|
|
|
|
- `RSNetInit` -> `RET`
|
|
- `RSNetRelease` -> `RET`
|
|
- `RSNetConnectionStart` -> `RET 0x4`
|
|
- `RSNetConnectionStartEx` -> `RET 0x4`
|
|
- `RSNetQueryRecord` -> `RET 0x8`
|
|
- `RSNetQueryRecordEx` -> `RET 0x8`
|
|
- `RSNetStartDownloadRecord` -> `RET 0x8`
|
|
- `RSNetStartDownloadRecordEx` -> `RET 0x8`
|
|
- `RSNetReqRecordData` -> `RET 0x4`
|
|
|
|
Practical implication:
|
|
|
|
- the first C# prototype should treat the exported functions as `CallingConvention.StdCall`
|
|
- the process must be 32-bit
|
|
|
|
### Runtime constraints for a C# host process
|
|
|
|
The managed host should be built and run as:
|
|
|
|
- `x86`, not `AnyCPU`
|
|
- Windows only
|
|
- from the vendor application directory, or with that directory added to the DLL search path
|
|
|
|
The safest deployment model is:
|
|
|
|
- place the C# test app in the same directory as `RSNet.dll`
|
|
- or launch it with the current directory set to `i:/Apps/Surveillance_client`
|
|
- or call `SetDllDirectory` / `AddDllDirectory` before loading `RSNet.dll`
|
|
|
|
Why that matters:
|
|
|
|
- `RSNet.dll` depends on the VC++ runtime and the rest of the vendor installation layout
|
|
- it also expects `RSNet.ini` nearby for logging/config initialization
|
|
- strings inside the DLL reference `RSP2PClient.exe` and `RSP2PDaemon.exe`, so P2P mode likely depends on those companion files being present
|
|
|
|
For direct-IP downloading, the minimum runtime context is likely smaller than the full client install, but the easiest first run is still to execute from the installed client folder.
|
|
|
|
## Recommended C# Loading Model
|
|
|
|
There are two realistic options.
|
|
|
|
### Option 1: Static `DllImport`
|
|
|
|
Use this once the calling convention and signatures are stable enough.
|
|
|
|
Advantages:
|
|
|
|
- simplest code
|
|
- easiest to read
|
|
- easiest to debug once structs are correct
|
|
|
|
Disadvantages:
|
|
|
|
- more brittle while struct layouts are still being discovered
|
|
|
|
### Option 2: `NativeLibrary.Load` plus delegates
|
|
|
|
Use this during the reverse-engineering phase.
|
|
|
|
Advantages:
|
|
|
|
- lets you experiment with signatures more safely
|
|
- avoids hard failure at process load if a dependency is missing
|
|
- makes it easy to log export resolution one function at a time
|
|
|
|
Disadvantages:
|
|
|
|
- more boilerplate
|
|
|
|
For the current stage, Option 2 is the better choice.
|
|
|
|
## C# Interop Types To Use
|
|
|
|
### Handles
|
|
|
|
Treat these as opaque `IntPtr` values:
|
|
|
|
- session handle returned by `RSNetConnectionStart*`
|
|
- download handle returned by `RSNetStartDownloadRecord*`
|
|
|
|
Do not model them as managed classes or attempt to dereference them directly in C#.
|
|
|
|
### Strings
|
|
|
|
Current evidence suggests the DLL uses ANSI `char *` strings, not UTF-16.
|
|
|
|
In C#:
|
|
|
|
- use `CharSet.Ansi`
|
|
- marshal strings as `LPStr`
|
|
- for uncertain layouts, prefer `IntPtr` plus `Marshal.StringToHGlobalAnsi`
|
|
|
|
### Struct packing
|
|
|
|
Use:
|
|
|
|
- `StructLayout(LayoutKind.Sequential, Pack = 4)`
|
|
|
|
Reason:
|
|
|
|
- this is a 32-bit Visual Studio binary using 4-byte pointers and ordinary 32-bit alignment
|
|
|
|
### Callbacks
|
|
|
|
Use delegates marked with:
|
|
|
|
```csharp
|
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
```
|
|
|
|
And keep the delegate alive for the entire native operation lifetime.
|
|
|
|
Practical rule:
|
|
|
|
- store callback delegates in fields, not local variables
|
|
|
|
Otherwise the GC may collect them while the native worker thread still holds the callback pointer.
|
|
|
|
## First-Pass C# API Shape
|
|
|
|
Based on the confirmed export behavior, a first interop layer can be modeled like this:
|
|
|
|
```csharp
|
|
internal static class RsNetNative
|
|
{
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
|
|
internal static extern void RSNetInit();
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern void RSNetRelease();
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern void RSNetSetEncription(uint value);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern IntPtr RSNetConnectionStart(ref RsNetConnectRequest request);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern IntPtr RSNetConnectionStartEx(IntPtr request);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern int RSNetQueryRecord(IntPtr session, IntPtr queryRequest);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern int RSNetQueryRecordEx(IntPtr session, IntPtr queryRequest);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern IntPtr RSNetStartDownloadRecord(IntPtr session, IntPtr downloadRequest);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern IntPtr RSNetStartDownloadRecordEx(IntPtr session, IntPtr downloadRequest);
|
|
|
|
[DllImport("RSNet.dll", CallingConvention = CallingConvention.StdCall)]
|
|
internal static extern void RSNetReqRecordData(IntPtr recordPlayHandle);
|
|
}
|
|
```
|
|
|
|
Notes:
|
|
|
|
- `RSNetConnectionStart` is the first function worth strongly typing
|
|
- `RSNetConnectionStartEx`, `RSNetQueryRecord*`, and `RSNetStartDownloadRecord*` should initially stay as `IntPtr` request pointers until the layouts are fully proven
|
|
- this reduces the chance of silent marshaling mistakes during the first prototype
|
|
|
|
## First Strongly-Typed C# Struct Worth Trying
|
|
|
|
The current best candidate for a partial typed struct is the direct-IP login request used by `RSNetConnectionStart`.
|
|
|
|
Best current guess:
|
|
|
|
```csharp
|
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
internal struct RsNetConnectRequest
|
|
{
|
|
public IntPtr AddressAnsi;
|
|
public ushort Port;
|
|
public ushort Padding0;
|
|
public IntPtr UsernameAnsi;
|
|
public IntPtr PasswordAnsi;
|
|
public uint Field10;
|
|
public uint Field14;
|
|
public uint Field18;
|
|
public uint Field1C;
|
|
public uint Field20;
|
|
public uint Field24;
|
|
public uint Field28;
|
|
}
|
|
```
|
|
|
|
Why this is reasonable:
|
|
|
|
- the decompiled function copies a pointer at offset `0x00`
|
|
- a 16-bit port value is read from offset `0x04`
|
|
- string pointers are copied from offsets `0x08` and `0x0c`
|
|
- the remaining fields are treated as 32-bit values or callback-related fields
|
|
|
|
This struct should still be treated as provisional.
|
|
|
|
## Likely C# Callback Shape
|
|
|
|
The session object stores either:
|
|
|
|
- a callback window handle and message ID
|
|
- or a callback function pointer plus a user/context value
|
|
|
|
The worker invokes the function callback in patterns like:
|
|
|
|
- `callback(code, userContext)`
|
|
- or posts a window message with `PostMessageA(hwnd, msgId, code, userContext)`
|
|
|
|
Best current guess for the login/status callback delegate:
|
|
|
|
```csharp
|
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
internal delegate void RsNetStatusCallback(uint code, IntPtr userContext);
|
|
```
|
|
|
|
This is not fully proven, but it matches the currently observed call sites better than any multi-parameter alternative.
|
|
|
|
## Why C# Is A Better Fit Than Raw Packet Reimplementation
|
|
|
|
For the current objective, C# plus unmanaged interop is likely better than reimplementing the network protocol because:
|
|
|
|
- the vendor DLL already knows the login handshake
|
|
- the vendor DLL already knows P2P versus direct-IP behavior
|
|
- the vendor DLL already manages record-query formatting
|
|
- the vendor DLL already performs the actual download worker loop
|
|
- the vendor DLL appears to write the file itself
|
|
|
|
That cuts the problem down to:
|
|
|
|
- loading the DLL safely
|
|
- building the request structs correctly
|
|
- handling callbacks/events
|
|
- waiting for the output file to appear and complete
|
|
|
|
## Recommended C# Downloader Architecture
|
|
|
|
### Layer 1: Native bootstrap
|
|
|
|
Responsibilities:
|
|
|
|
- locate the vendor install directory
|
|
- set DLL search path
|
|
- call `RSNetInit`
|
|
- optionally call `RSNetSetEncription`
|
|
|
|
### Layer 2: Session wrapper
|
|
|
|
Responsibilities:
|
|
|
|
- allocate unmanaged ANSI strings for device address, username, password
|
|
- build the login request
|
|
- call `RSNetConnectionStart`
|
|
- keep the callback delegate alive
|
|
- expose a `Task` or `ManualResetEventSlim` for login completion
|
|
|
|
### Layer 3: Record query wrapper
|
|
|
|
Responsibilities:
|
|
|
|
- build an unmanaged query struct
|
|
- invoke `RSNetQueryRecord`
|
|
- collect returned `0x1c` entries into managed record models
|
|
- expose them to higher-level range selection code
|
|
|
|
### Layer 4: Download wrapper
|
|
|
|
Responsibilities:
|
|
|
|
- choose the target record entry or entries
|
|
- build the native download request and per-item metadata
|
|
- call `RSNetStartDownloadRecord`
|
|
- monitor status/progress callbacks
|
|
- resolve the final output file path
|
|
|
|
### Layer 5: Cleanup
|
|
|
|
Responsibilities:
|
|
|
|
- call the native stop/cleanup entrypoints when known
|
|
- free all unmanaged strings and unmanaged buffers
|
|
- call `RSNetRelease` on process shutdown
|
|
|
|
### What a minimal C# prototype should look like
|
|
|
|
The first prototype should not attempt the whole workflow at once.
|
|
|
|
Do it in stages:
|
|
|
|
1. Load `RSNet.dll` successfully from C# in an `x86` process.
|
|
2. Call `RSNetInit()` and verify it does not crash.
|
|
3. Call `RSNetConnectionStart(...)` with a direct-IP request and log callback codes.
|
|
4. Confirm which callback code means login success.
|
|
5. Add `RSNetQueryRecord(...)` and dump raw `0x1c` entries.
|
|
6. Only then attempt `RSNetStartDownloadRecord(...)`.
|
|
|
|
That staged approach is important because the unstable part is the native struct layout, not the export resolution.
|
|
|
|
## Risks Specific To C# Interop
|
|
|
|
The main failure modes are:
|
|
|
|
- running as `AnyCPU` or `x64` and failing to load the 32-bit DLL
|
|
- incorrect calling convention
|
|
- incorrect struct packing
|
|
- wrong ANSI versus Unicode marshaling
|
|
- GC moving or collecting delegates or unmanaged buffers too early
|
|
- freeing unmanaged strings before the async worker is finished using them
|
|
- assuming login is synchronous when it is not
|
|
|
|
Operational rule:
|
|
|
|
- keep every unmanaged buffer and every callback delegate alive until the native session or download handle has definitively finished
|
|
|
|
## Implementation Checklist For A C# Version
|
|
|
|
Before coding:
|
|
|
|
- confirm the C# host runs as `x86`
|
|
- run from the vendor install folder or configure DLL search paths explicitly
|
|
- keep a copy of the vendor folder structure intact for first tests
|
|
|
|
During interop setup:
|
|
|
|
- use `StdCall`
|
|
- use `CharSet.Ansi`
|
|
- use `Pack = 4`
|
|
- represent unknown native structs as `IntPtr` first
|
|
- keep callback delegates pinned by strong references
|
|
|
|
During login:
|
|
|
|
- prefer `RSNetConnectionStart` over `RSNetConnectionStartEx`
|
|
- use direct IP and media port first
|
|
- do not assume immediate success after the function returns a non-null session handle
|
|
|
|
During record query:
|
|
|
|
- expect asynchronous or callback-driven result delivery
|
|
- log raw bytes for each `0x1c` record entry before trying to reinterpret every field semantically
|
|
|
|
During download:
|
|
|
|
- start with one record entry, not a batch
|
|
- expect completion via status event rather than via the return value alone
|
|
- inspect the output directory for the file the DLL creates
|
|
|
|
## What Still Needs To Be Recovered For A Clean C# Implementation
|
|
|
|
The unresolved items are the same as for Python FFI, but C# can tolerate them if opaque pointers are used first.
|
|
|
|
The most important remaining tasks are:
|
|
|
|
1. exact `RSNetConnectionStart` request struct validation
|
|
2. exact query-request struct layout
|
|
3. exact query callback prototype
|
|
4. exact download-request struct layout
|
|
5. exact per-item `0x108` metadata layout
|
|
6. exact stop-download export signature
|
|
7. exact mapping from numeric event codes to login/query/download states
|
|
|
|
## Revised Assessment
|
|
|
|
Yes, a C# implementation is realistic.
|
|
|
|
It is probably one of the better options if the end goal is a Windows-only utility that uses the vendor binary as-is.
|
|
|
|
The current blocker is not `can C# do it?` but `how much of the request structs do we want to strongly type before writing the prototype?`
|
|
|
|
At this point, a cautious C# prototype is feasible if it:
|
|
|
|
- uses `x86`
|
|
- calls the DLL with `StdCall`
|
|
- uses direct IP login first
|
|
- treats most request blobs as unmanaged buffers until the layouts are fully validated
|
|
|
|
### Recommended first prototype flow
|
|
|
|
1. Load `RSNet.dll` with `ctypes.WinDLL`
|
|
2. Call `RSNetInit()`
|
|
3. Optionally call `RSNetSetEncription(...)` if required by the target device family
|
|
4. Build a direct-IP login struct for `RSNetConnectionStart`
|
|
5. Wait until the async login callback reports success
|
|
6. Build a record-query request for `RSNetQueryRecord`
|
|
7. Collect returned `0x1c` record entries
|
|
8. Pick the entries intersecting the user-requested time range
|
|
9. Build a single-entry download request and call `RSNetStartDownloadRecord`
|
|
10. Wait for `RSNetMsgDownloadRecordOK` or a terminal failure event
|
|
11. Locate the output file written by the DLL
|
|
12. Release/cleanup handles
|
|
|
|
### Why direct-IP first
|
|
|
|
The `Ex` login path clearly has extra logic for P2P or relay modes.
|
|
|
|
To keep the prototype small:
|
|
|
|
- use direct device IP / media port first
|
|
- defer P2P-only targets until after the basic downloader works
|
|
|
|
## Best Current Guess At Minimal Script Design
|
|
|
|
### Language
|
|
|
|
Python with `ctypes` is the fastest path.
|
|
|
|
C# is now a credible alternative for a Windows-only implementation that loads the vendor DLL directly.
|
|
|
|
### Components
|
|
|
|
1. DLL loader and function prototypes
|
|
2. callback function declared with `ctypes.WINFUNCTYPE`
|
|
3. login request struct for `RSNetConnectionStart`
|
|
4. query request struct for `RSNetQueryRecord`
|
|
5. query result callback handling `0x1c` entries
|
|
6. download request struct for `RSNetStartDownloadRecord`
|
|
7. event loop or polling wait for download completion
|
|
|
|
### Important caveat
|
|
|
|
The exact `ctypes.Structure` layouts are not fully proven yet.
|
|
|
|
So the first implementation should be treated as an iterative prototype, not a guaranteed drop-in script.
|
|
|
|
Still, the reverse engineering so far is enough to support the following plan:
|
|
|
|
- prototype direct login with `RSNetConnectionStart`
|
|
- verify callback codes for login success and failure
|
|
- prototype `RSNetQueryRecord` with a callback that logs each `0x1c` result entry
|
|
- once one real query works, use a single returned record entry as input to `RSNetStartDownloadRecord`
|
|
|
|
That is now a tractable task.
|
|
|
|
## Gaps That Still Matter Before Writing The Actual Script
|
|
|
|
These are the highest-value unresolved items:
|
|
|
|
1. Exact layout of the `RSNetConnectionStart` input struct
|
|
2. Exact layout of the `RSNetQueryRecord` request struct and callback prototype
|
|
3. Exact layout of the `RSNetStartDownloadRecord` request struct, especially the destination-file metadata
|
|
4. Exact numeric callback codes corresponding to:
|
|
- login success
|
|
- login failure types
|
|
- query complete
|
|
- download progress
|
|
- download success
|
|
- download failure
|
|
5. Exact output file naming and format-selection rules
|
|
|
|
## Recommended Next Reverse-Engineering Steps
|
|
|
|
The next pass should focus on making the prototype script concrete:
|
|
|
|
1. Rename key `RSNet.dll` internals:
|
|
- `FUN_100050a0`
|
|
- `FUN_100051f0`
|
|
- `FUN_10005ca0`
|
|
- `FUN_10005eb0`
|
|
- `FUN_10010330`
|
|
- `FUN_100103b0`
|
|
- `FUN_100105b0`
|
|
- `FUN_100074b0`
|
|
|
|
2. Recover the exact login callback prototype from the session object fields around `+0x68`, `+0x6c`, `+0x70`, and `+0x78`
|
|
|
|
3. Recover the exact query callback prototype used by result handlers `FUN_100046b0` and `FUN_10004750`
|
|
|
|
4. Identify the structure at size `0x108` associated with each download item
|
|
|
|
5. Identify where the output file path is copied into the download-item metadata
|
|
|
|
6. Validate the call sequence against a real device using credentials
|
|
|
|
## Current Assessment
|
|
|
|
The original objective was to analyze until it became possible to figure out how to make a simple script that downloads a video file.
|
|
|
|
That threshold has been reached in architectural terms:
|
|
|
|
- use `RSNet.dll` directly
|
|
- direct-IP login first
|
|
- query recordings first
|
|
- submit one record entry into the download API
|
|
- let the DLL write the file locally
|
|
- monitor callback/status events for completion
|
|
|
|
The remaining work is no longer “what is the path?” but “what are the exact struct layouts?”
|
|
|
|
That is a much narrower problem and should be solvable in a follow-up pass, especially with live credentials or one round of prototype-and-adjust testing. |