Skip to content

Commit 7c9ab40

Browse files
committed
streamline node package
1 parent ff5ef81 commit 7c9ab40

File tree

17 files changed

+633
-573
lines changed

17 files changed

+633
-573
lines changed

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
"Bash(sudo cargo run:*)",
1616
"Bash(sudo timeout 10 cargo run:*)",
1717
"Bash(curl:*)",
18-
"Bash(sudo node:*)"
18+
"Bash(sudo node:*)",
19+
"Bash(npm run build:*)",
20+
"Bash(sudo npm test:*)",
21+
"Bash(sudo npm run demo:*)"
1922
]
2023
}
2124
}

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ path = "src/lib.rs"
1212
default = ["mono", "il2cpp"]
1313
mono = []
1414
il2cpp = []
15+
napi-bindings = ["napi", "napi-derive"]
1516

1617
[dependencies]
1718
is_elevated = "0.1.2"
1819
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
19-
napi = { version = "2.12.2", default-features = false, features = ["napi4", "serde-json"] }
20-
napi-derive = "2.12.2"
20+
napi = { version = "2.12.2", default-features = false, features = ["napi4", "serde-json"], optional = true }
21+
napi-derive = { version = "2.12.2", optional = true }
2122
process-memory = "0.5.0"
2223
read-process-memory = "0.1.6"
2324
serde_json = "1.0.116"
@@ -39,6 +40,7 @@ winapi = { version = "0.3", features = ["psapi", "processthreadsapi", "handleapi
3940
[target.'cfg(target_os = "macos")'.dependencies]
4041
mach2 = "0.4"
4142
libproc = "0.14"
43+
libc = "0.2"
4244

4345
[build-dependencies]
4446
napi-build = "2.0.1"
Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/* Auto-generated TypeScript definitions for mtga-reader-node */
1+
/**
2+
* mtga-reader - Node.js bindings for reading Magic: The Gathering Arena memory
3+
*/
24

35
export interface ClassInfo {
46
name: string;
@@ -33,7 +35,7 @@ export interface InstanceField {
3335
name: string;
3436
typeName: string;
3537
isStatic: boolean;
36-
value: any;
38+
value: unknown;
3739
}
3840

3941
export interface InstanceData {
@@ -44,35 +46,33 @@ export interface InstanceData {
4446
}
4547

4648
export interface DictionaryEntry {
47-
key: any;
48-
value: any;
49+
key: unknown;
50+
value: unknown;
4951
}
5052

5153
export interface DictionaryData {
5254
count: number;
5355
entries: DictionaryEntry[];
5456
}
5557

56-
// Utility functions
57-
5858
/**
59-
* Check if the current process has administrator privileges
59+
* Check if running with admin/root privileges
6060
*/
6161
export function isAdmin(): boolean;
6262

6363
/**
64-
* Find a process by name and return true if found
64+
* Check if a process with the given name is running
6565
*/
6666
export function findProcess(processName: string): boolean;
6767

6868
/**
69-
* Initialize connection to the target process
70-
* Must be called before using any other reader functions
69+
* Initialize the reader for a given process
70+
* @throws Error if process not found or initialization fails
7171
*/
7272
export function init(processName: string): boolean;
7373

7474
/**
75-
* Close the connection to the target process
75+
* Close the reader and release resources
7676
*/
7777
export function close(): boolean;
7878

@@ -81,61 +81,54 @@ export function close(): boolean;
8181
*/
8282
export function isInitialized(): boolean;
8383

84-
// Assembly functions
85-
8684
/**
87-
* Get all loaded assembly names
85+
* Get list of loaded assemblies
8886
*/
8987
export function getAssemblies(): string[];
9088

9189
/**
92-
* Get all classes in an assembly
90+
* Get classes in an assembly
9391
*/
9492
export function getAssemblyClasses(assemblyName: string): ClassInfo[];
9593

9694
/**
97-
* Get detailed information about a class
95+
* Get detailed class information including fields
9896
*/
9997
export function getClassDetails(assemblyName: string, className: string): ClassDetails;
10098

101-
// Instance reading functions
102-
10399
/**
104-
* Read an instance at a given memory address
100+
* Get instance data at a memory address
105101
*/
106102
export function getInstance(address: number): InstanceData;
107103

108104
/**
109-
* Read a specific field from an instance
105+
* Get a specific field from an instance
110106
*/
111-
export function getInstanceField(address: number, fieldName: string): any;
107+
export function getInstanceField(address: number, fieldName: string): unknown;
112108

113109
/**
114-
* Read a static field from a class
110+
* Get a static field from a class
115111
*/
116-
export function getStaticField(classAddress: number, fieldName: string): any;
117-
118-
// Dictionary reading
112+
export function getStaticField(classAddress: number, fieldName: string): unknown;
119113

120114
/**
121-
* Read a dictionary at a given memory address
115+
* Read a dictionary at a memory address
122116
*/
123117
export function getDictionary(address: number): DictionaryData;
124118

125-
// High-level data reading
126-
127119
/**
128-
* Read nested data by traversing a path of field names
129-
* The first element is the root class name, subsequent elements are field names
120+
* Legacy API: Read data by following a path of field names
121+
* Windows: WrapperController path (WrapperController.Instance.InventoryManager...)
122+
* macOS: PAPA path (PAPA._InventoryManager.GetPlayerCardsNoLock...)
130123
*/
131-
export function readData(processName: string, fields: string[]): any;
124+
export function readData(processName: string, fields: string[]): unknown;
132125

133126
/**
134-
* Read a managed class at a given address
127+
* Legacy API: Read class at address
135128
*/
136-
export function readClass(processName: string, address: number): any;
129+
export function readClass(processName: string, address: number): unknown;
137130

138131
/**
139-
* Read a generic instance at a given address
132+
* Legacy API: Read generic instance at address
140133
*/
141-
export function readGenericInstance(processName: string, address: number): any;
134+
export function readGenericInstance(processName: string, address: number): unknown;

mtga-reader-js/index.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* mtga-reader - Node.js bindings for reading Magic: The Gathering Arena memory
3+
*
4+
* Cross-platform support:
5+
* - Windows: Uses Mono backend
6+
* - macOS: Uses IL2CPP backend
7+
*/
8+
9+
const path = require('path');
10+
const fs = require('fs');
11+
12+
// Try to load the native addon
13+
let nativeBinding = null;
14+
15+
// Possible locations for the .node file
16+
const possiblePaths = [
17+
// Same directory as index.js
18+
path.join(__dirname, 'mtga_reader.node'),
19+
// Platform-specific naming
20+
path.join(__dirname, `mtga_reader.${process.platform}-${process.arch}.node`),
21+
// Build output from cargo (release)
22+
path.join(__dirname, '..', 'target', 'release', 'libmtga_reader.dylib'),
23+
path.join(__dirname, '..', 'target', 'release', 'mtga_reader.dll'),
24+
];
25+
26+
for (const bindingPath of possiblePaths) {
27+
if (fs.existsSync(bindingPath)) {
28+
try {
29+
nativeBinding = require(bindingPath);
30+
break;
31+
} catch (e) {
32+
// Try next path
33+
}
34+
}
35+
}
36+
37+
if (!nativeBinding) {
38+
throw new Error(
39+
`Failed to load native binding. Tried:\n${possiblePaths.join('\n')}\n\n` +
40+
'Please ensure the native addon is built. Run: npm run build'
41+
);
42+
}
43+
44+
// Export all functions from the native binding
45+
module.exports = {
46+
/**
47+
* Check if running with admin/root privileges
48+
* @returns {boolean}
49+
*/
50+
isAdmin: nativeBinding.isAdmin,
51+
52+
/**
53+
* Check if a process with the given name is running
54+
* @param {string} processName - Name of the process to find
55+
* @returns {boolean}
56+
*/
57+
findProcess: nativeBinding.findProcess,
58+
59+
/**
60+
* Initialize the reader for a given process
61+
* @param {string} processName - Name of the process (e.g., "MTGA")
62+
* @returns {boolean} - True if initialization succeeded
63+
* @throws {Error} - If process not found or initialization fails
64+
*/
65+
init: nativeBinding.init,
66+
67+
/**
68+
* Close the reader and release resources
69+
* @returns {boolean}
70+
*/
71+
close: nativeBinding.close,
72+
73+
/**
74+
* Check if the reader is initialized
75+
* @returns {boolean}
76+
*/
77+
isInitialized: nativeBinding.isInitialized,
78+
79+
/**
80+
* Get list of loaded assemblies
81+
* @returns {string[]}
82+
*/
83+
getAssemblies: nativeBinding.getAssemblies,
84+
85+
/**
86+
* Get classes in an assembly
87+
* @param {string} assemblyName - Name of the assembly
88+
* @returns {ClassInfo[]}
89+
*/
90+
getAssemblyClasses: nativeBinding.getAssemblyClasses,
91+
92+
/**
93+
* Get detailed class information including fields
94+
* @param {string} assemblyName - Name of the assembly
95+
* @param {string} className - Name of the class
96+
* @returns {ClassDetails}
97+
*/
98+
getClassDetails: nativeBinding.getClassDetails,
99+
100+
/**
101+
* Get instance data at a memory address
102+
* @param {number} address - Memory address of the instance
103+
* @returns {InstanceData}
104+
*/
105+
getInstance: nativeBinding.getInstance,
106+
107+
/**
108+
* Get a specific field from an instance
109+
* @param {number} address - Memory address of the instance
110+
* @param {string} fieldName - Name of the field
111+
* @returns {any} - Field value
112+
*/
113+
getInstanceField: nativeBinding.getInstanceField,
114+
115+
/**
116+
* Get a static field from a class
117+
* @param {number} classAddress - Memory address of the class
118+
* @param {string} fieldName - Name of the static field
119+
* @returns {any} - Field value
120+
*/
121+
getStaticField: nativeBinding.getStaticField,
122+
123+
/**
124+
* Read a dictionary at a memory address
125+
* @param {number} address - Memory address of the dictionary
126+
* @returns {DictionaryData}
127+
*/
128+
getDictionary: nativeBinding.getDictionary,
129+
130+
/**
131+
* Legacy API: Read data by following a path of field names
132+
* @param {string} processName - Process name
133+
* @param {string[]} fields - Array of field names to traverse
134+
* @returns {any} - The value at the end of the path
135+
*/
136+
readData: nativeBinding.readData,
137+
138+
/**
139+
* Legacy API: Read class at address
140+
* @param {string} processName - Process name
141+
* @param {number} address - Memory address
142+
* @returns {any}
143+
*/
144+
readClass: nativeBinding.readClass,
145+
146+
/**
147+
* Legacy API: Read generic instance at address
148+
* @param {string} processName - Process name
149+
* @param {number} address - Memory address
150+
* @returns {any}
151+
*/
152+
readGenericInstance: nativeBinding.readGenericInstance,
153+
};
154+
155+
/**
156+
* @typedef {Object} ClassInfo
157+
* @property {string} name - Class name
158+
* @property {string} namespace - Class namespace
159+
* @property {number} address - Memory address
160+
* @property {boolean} isStatic - Is static class
161+
* @property {boolean} isEnum - Is enum type
162+
*/
163+
164+
/**
165+
* @typedef {Object} FieldInfo
166+
* @property {string} name - Field name
167+
* @property {string} typeName - Field type name
168+
* @property {number} offset - Field offset
169+
* @property {boolean} isStatic - Is static field
170+
* @property {boolean} isConst - Is const field
171+
*/
172+
173+
/**
174+
* @typedef {Object} StaticInstanceInfo
175+
* @property {string} fieldName - Static field name
176+
* @property {number} address - Instance address
177+
*/
178+
179+
/**
180+
* @typedef {Object} ClassDetails
181+
* @property {string} name - Class name
182+
* @property {string} namespace - Class namespace
183+
* @property {number} address - Class address
184+
* @property {FieldInfo[]} fields - Class fields
185+
* @property {StaticInstanceInfo[]} staticInstances - Static instances
186+
*/
187+
188+
/**
189+
* @typedef {Object} InstanceField
190+
* @property {string} name - Field name
191+
* @property {string} typeName - Field type
192+
* @property {boolean} isStatic - Is static
193+
* @property {any} value - Field value
194+
*/
195+
196+
/**
197+
* @typedef {Object} InstanceData
198+
* @property {string} className - Class name
199+
* @property {string} namespace - Namespace
200+
* @property {number} address - Instance address
201+
* @property {InstanceField[]} fields - Instance fields
202+
*/
203+
204+
/**
205+
* @typedef {Object} DictionaryEntry
206+
* @property {any} key - Entry key
207+
* @property {any} value - Entry value
208+
*/
209+
210+
/**
211+
* @typedef {Object} DictionaryData
212+
* @property {number} count - Number of entries
213+
* @property {DictionaryEntry[]} entries - Dictionary entries
214+
*/

0 commit comments

Comments
 (0)