aboutsummaryrefslogtreecommitdiff
path: root/tools/convert-unreal-tkl
blob: ad55e2f9dfbf2b48134bf6b814de0c375980fde6 (about) (plain) (blame)
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
#!/usr/bin/env python3
#
# InspIRCd -- Internet Relay Chat Daemon
#
#   Copyright (C) 2024 Val Lorentz <progval+git@progval.net>
#   Copyright (C) 2024 Sadie Powell <sadie@witchery.services>
#
# This file is part of InspIRCd.  InspIRCd is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#


import io
import os
import sys


class UnrealDB:
    def __init__(self, path):
        self.error = None
        try:
            with open(sys.argv[1], mode="rb") as fh:
                data = fh.read()
                if data.startswith(b"UnrealIRCd-DB-v1"):
                    self.data = io.BytesIO(data[40:])
                elif data.startswith(b"UnrealIRCd-DB"):
                    self.error = f"Unsupported database version: {data[0:32]}"
                else:
                    self.data = io.BytesIO(data)
        except OSError as e:
            self.error = f"Read error: {e}"

    def read_char(self):
        return self.data.read(1).decode("utf-8")

    def read_i16(self):
        return int.from_bytes(self.data.read(2), byteorder="little")

    def read_i32(self):
        return int.from_bytes(self.data.read(4), byteorder="little")

    def read_i64(self):
        return int.from_bytes(self.data.read(8), byteorder="little")

    def read_str(self):
        len = self.read_i16()
        if len == 0 or len == 0xFFFF:
            return ""
        return self.data.read(len).decode("utf-8")


def error(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)


if len(sys.argv) < 3:
    program = os.path.basename(__file__)
    error(f"Usage: {program} <input-file> <output-file>")

db = UnrealDB(sys.argv[1])
if db.error:
    error(db.error)

magic = db.read_i32()
if magic != 0x10101010:
    error(f"Unrecognised database magic: {magic}")

version = db.read_i32()
if version != 4999:
    error(f"Unrecognised TKL database version: {version}")

count = db.read_i64()
print(f"Converting {count} TKLs ...")

try:
    with open(sys.argv[2], mode="w") as fh:
        print("VERSION 1", file=fh)
        for i in range(count):
            type = db.read_char()
            set_by = db.read_str()
            set_at = db.read_i64()
            expire_at = db.read_i64()
            duration = expire_at - set_at

            if type in "Ee":
                username = db.read_str()
                hostname = db.read_str()
                etype = db.read_str()  # unused
                reason = db.read_str()
                print(
                    f"LINE E {username}@{hostname} {set_by} {set_at} {duration} :{reason}",
                    file=fh,
                )
            elif type in "Ff":
                method = db.read_str()  # unused
                pattern = db.read_str()  # unused
                target = db.read_str()  # unused
                action = db.read_char()  # unused
                reason = db.read_str()  # unused
                duration = db.read_i64()  # unused
            elif type in "GksZz":
                username = db.read_str()
                hostname = db.read_str()
                reason = db.read_str()
                line = "SHUN" if type == "s" else type.upper()
                print(
                    f"LINE {line} {username}@{hostname} {set_by} {set_at} {duration} :{reason}",
                    file=fh,
                )
            elif type in "Qq":
                etype = db.read_str()  # unused
                name = db.read_str()
                reason = db.read_str()
                print(f"LINE Q {name} {set_by} {set_at} {duration} :{reason}", file=fh)
            else:
                error(f"Unsupported row type: {type}")

except OSError as e:
    error(f"Write error: {e}")