Skip to content

Commit b0d2f6a

Browse files
committed
feat: Start looking at non RLL routines and add log tp
1 parent c91a609 commit b0d2f6a

File tree

9 files changed

+3332
-35
lines changed

9 files changed

+3332
-35
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ ExtractAcdDatabaseRecordsToFiles('CuteLogix.ACD', 'output_directory').extract()
6161
### Dump Comps Database Records
6262

6363
The Comps database contains a lot of information and can be export as a directory structure to make it easier to look at.
64+
It will also extract the CIP class and instance and write it to the log file.
6465

6566
```python
6667
from acd.api import DumpCompsRecordsToFile

acd/api.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
from dataclasses import dataclass
23
from os import PathLike
34
from typing import List
@@ -118,4 +119,5 @@ class DumpCompsRecordsToFile(ExportProject):
118119

119120
def extract(self):
120121
export = ExportL5x(self.filename)
121-
DumpCompsRecords(export._cur, 0).dump(0)
122+
with open(os.path.join(self.output_directory, export.project.target_name + ".log"), "w") as log_file:
123+
DumpCompsRecords(export._cur, 0).dump(log_file=log_file)

acd/generated/comps/rx_generic.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ def _read(self):
2121
self.cip_type = self._io.read_u2le()
2222
self.comment_id = self._io.read_u2le()
2323
_on = self.cip_type
24-
if _on == 107:
24+
if _on == 104:
25+
self._raw_main_record = self._io.read_bytes(60)
26+
_io__raw_main_record = KaitaiStream(BytesIO(self._raw_main_record))
27+
self.main_record = RxGeneric.RxTag(_io__raw_main_record, self, self._root)
28+
elif _on == 107:
2529
self._raw_main_record = self._io.read_bytes(60)
2630
_io__raw_main_record = KaitaiStream(BytesIO(self._raw_main_record))
2731
self.main_record = RxGeneric.RxTag(_io__raw_main_record, self, self._root)
@@ -258,4 +262,15 @@ def _read(self):
258262
self.value = self._io.read_bytes(self.len_value)
259263

260264

265+
@property
266+
def record_buffer(self):
267+
if hasattr(self, '_m_record_buffer'):
268+
return self._m_record_buffer
269+
270+
_pos = self._io.pos()
271+
self._io.seek(14)
272+
self._m_record_buffer = self._io.read_bytes(60)
273+
self._io.seek(_pos)
274+
return getattr(self, '_m_record_buffer', None)
275+
261276

acd/l5x/elements.py

+50-20
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@
1010
from datetime import datetime, timedelta
1111
import xml.etree.ElementTree as ET
1212

13-
from acd.exceptions.CompsRecordException import UnknownRxTagVersion
1413
from acd.generated.comps.rx_generic import RxGeneric
15-
from acd.generated.comps.rx_tag import RxTag
16-
from acd.generated.controller.rx_controller import RxController
17-
from acd.generated.map_device.rx_map_device import RxMapDevice
1814

15+
from loguru import logger as log
1916

2017
@dataclass
2118
class L5xElementBuilder:
@@ -39,7 +36,7 @@ def to_xml(self):
3936
if isinstance(attribute_value, L5xElement):
4037
child_list.append(attribute_value.to_xml())
4138
elif isinstance(attribute_value, list):
42-
if attribute == "tags" or attribute == "data_types" or attribute == "members":
39+
if attribute == "tags" or attribute == "data_types" or attribute == "members" or attribute == "programs" or attribute == "routines":
4340
new_child_list: List[str] = []
4441
for element in attribute_value:
4542
if isinstance(element, L5xElement):
@@ -99,6 +96,8 @@ class MapDevice(L5xElement):
9996

10097
@dataclass
10198
class Routine(L5xElement):
99+
name: str
100+
type: str
102101
rungs: List[str]
103102

104103

@@ -137,7 +136,7 @@ class RSLogix5000Content(L5xElement):
137136
schema_revision: str
138137
software_revision: str
139138
target_name: str
140-
target_name: str
139+
target_type: str
141140
contains_context: str
142141
export_date: str
143142
export_options: str
@@ -336,16 +335,16 @@ def build(self) -> Tag:
336335
except Exception as e:
337336
return Tag(results[0][0], results[0][0], "Base", "", "Decimal", "None", 0, [])
338337

339-
if r.cip_type != 0x6B:
338+
if r.cip_type != 0x6B and r.cip_type != 0x68:
340339
return Tag(results[0][0], results[0][0], "Base", "", "Decimal", "None", 0, [])
341340
if r.main_record.data_type == 0xFFFFFFFF:
342-
return Tag(results[0][0], results[0][0], "Base", "", "Decimal", "None", r.main_record.data_table_instance, [])
343-
344-
self._cur.execute(
345-
"SELECT comp_name, object_id, parent_id, record FROM comps WHERE object_id=" + str(
346-
r.main_record.data_type))
347-
data_type_results = self._cur.fetchall()
348-
data_type = data_type_results[0][0]
341+
data_type = ""
342+
else:
343+
self._cur.execute(
344+
"SELECT comp_name, object_id, parent_id, record FROM comps WHERE object_id=" + str(
345+
r.main_record.data_type))
346+
data_type_results = self._cur.fetchall()
347+
data_type = data_type_results[0][0]
349348

350349
self._cur.execute(
351350
"SELECT tag_reference, record_string FROM comments WHERE parent=" + str(
@@ -372,6 +371,24 @@ def build(self) -> Tag:
372371
return Tag(name, name, "Base", data_type, radix, external_access, r.main_record.data_table_instance, comment_results)
373372

374373

374+
def routine_type_enum(idx: int) -> str:
375+
if idx == 0:
376+
return "TypeLess"
377+
if idx == 1:
378+
return "RLL"
379+
if idx == 2:
380+
return "FBD"
381+
if idx == 3:
382+
return "SFC"
383+
if idx == 4:
384+
return "ST"
385+
if idx == 5:
386+
return "External"
387+
if idx == 6:
388+
return "Encrypted"
389+
return "Typeless"
390+
391+
375392
@dataclass
376393
class RoutineBuilder(L5xElementBuilder):
377394

@@ -381,8 +398,14 @@ def build(self) -> Routine:
381398
self._object_id))
382399
results = self._cur.fetchall()
383400

401+
try:
402+
r = RxGeneric.from_bytes(results[0][3])
403+
except Exception as e:
404+
return Routine(results[0][0], results[0][0], "", [])
405+
384406
record = results[0][3]
385407
name = results[0][0]
408+
routine_type = routine_type_enum(struct.unpack_from("<H", r.record_buffer, 0x30)[0])
386409

387410
self._cur.execute(
388411
"SELECT object_id, parent_id, seq_no FROM region_map WHERE parent_id=" + str(
@@ -396,7 +419,7 @@ def build(self) -> Routine:
396419
rungs_results = self._cur.fetchall()
397420
if len(rungs_results) > 0:
398421
rungs.append(rungs_results[0][1])
399-
return Routine(name, rungs)
422+
return Routine(name, name, routine_type, rungs)
400423

401424

402425
@dataclass
@@ -459,6 +482,8 @@ def build(self) -> Program:
459482
self._object_id))
460483
results = self._cur.fetchall()
461484

485+
r = RxGeneric.from_bytes(results[0][3])
486+
462487
name = results[0][0]
463488

464489
self._cur.execute(
@@ -492,6 +517,11 @@ def build(self) -> Program:
492517
for result in results:
493518
tags.append(TagBuilder(self._cur, result[1]).build())
494519

520+
self._cur.execute(
521+
"SELECT tag_reference, record_string FROM comments WHERE parent=" + str(
522+
(r.comment_id * 0x10000) + r.cip_type))
523+
comment_results = self._cur.fetchall()
524+
495525
return Program(name, routines, tags)
496526

497527

@@ -641,14 +671,14 @@ def build(self) -> RSLogix5000Content:
641671
now = datetime.now()
642672
export_date = now.strftime("%a %b %d %H:%M:%S %Y")
643673
export_options = "NoRawData L5KData DecoratedData ForceProtectedEncoding AllProjDocTrans"
644-
return RSLogix5000Content(None, schema_revision, software_revision, target_name, target_type, contains_context, export_date, export_options)
674+
return RSLogix5000Content(target_name, None, schema_revision, software_revision, target_name, target_type, contains_context, export_date, export_options)
645675

646676

647677
@dataclass
648678
class DumpCompsRecords(L5xElementBuilder):
649679
base_directory: PathLike = Path("dump")
650680

651-
def dump(self, parent_id: int = 0):
681+
def dump(self, parent_id: int = 0, log_file=None):
652682
self._cur.execute(
653683
f"SELECT comp_name, object_id, parent_id, record_type, record FROM comps WHERE parent_id={parent_id}")
654684
results = self._cur.fetchall()
@@ -663,8 +693,8 @@ def dump(self, parent_id: int = 0):
663693
if not os.path.exists(os.path.join(new_path)):
664694
os.makedirs(new_path)
665695
with open(Path(os.path.join(new_path, name + ".dat")), "wb") as file:
696+
log_file.write(
697+
f"Class - {struct.unpack_from('<H', result[4], 0xA)[0]} Instance {struct.unpack_from('<H', result[4], 0xC)[0]}- {str(new_path) + '/' + name}\n")
666698
file.write(record)
667699

668-
DumpCompsRecords(self._cur, object_id, new_path).dump(object_id)
669-
670-
700+
DumpCompsRecords(self._cur, object_id, new_path).dump(object_id, log_file)

acd/nameless.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ class NamelessRecord:
1212
dat_record: DatRecord
1313

1414
def __post_init__(self):
15-
if self.dat_record.identifier == b'\xfa\xfa':
15+
if self.dat_record.identifier == 64250:
1616
identifier_offset = 8
1717
self.identifier = struct.unpack(
18-
"I", self.dat_record.record[identifier_offset : identifier_offset + 4]
18+
"I", self.dat_record.record.record_buffer[identifier_offset : identifier_offset + 4]
1919
)[0]
2020

21-
not_sure_offset = 12
22-
self.not_sure = struct.unpack(
23-
"I", self.dat_record.record[not_sure_offset: not_sure_offset + 4]
24-
)[0]
21+
object_identifier_offset = 0x0C
22+
self.object_identifier = struct.unpack_from("<I", self.dat_record.record.record_buffer, object_identifier_offset)[0]
2523

2624
query: str = "INSERT INTO nameless VALUES (?, ?, ?)"
27-
enty: tuple = (self.identifier, self.not_sure, self.dat_record.record)
25+
enty: tuple = (self.object_identifier, self.identifier, self.dat_record.record.record_buffer)
2826
self._cur.execute(query, enty)

acd/sbregion.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def __post_init__(self):
1919
else:
2020
return
2121

22-
if r.header.language_type == "Rung NT":
22+
if r.header.language_type == "Rung NT" or r.header.language_type == "REGION NT":
2323
text = r.record_buffer.decode("utf-16-le").rstrip('\x00')
2424
self.text = self.replace_tag_references(text)
2525

@@ -29,6 +29,9 @@ def __post_init__(self):
2929
self._cur.execute(query, entry)
3030
elif r.header.language_type == "REGION AST":
3131
pass
32+
elif r.header.language_type == "REGION LE UID":
33+
uuid = struct.unpack("<I", r.record_buffer[-4:])[0]
34+
pass
3235
else:
3336
pass
3437

@@ -40,5 +43,7 @@ def replace_tag_references(self, sb_rec):
4043
tag_id = int(tag_no, 16)
4144
self._cur.execute("SELECT object_id, comp_name FROM comps WHERE object_id=" + str(tag_id))
4245
results = self._cur.fetchall()
46+
if len(results) == 0:
47+
return sb_rec
4348
sb_rec = sb_rec.replace(tag, results[0][1])
4449
return sb_rec

0 commit comments

Comments
 (0)