annotate pytouhou/formats/score.py @ 612:73f134f84c7f

Request a RGB888 context, since SDL2’s default of RGB332 sucks. On X11/GLX, it will select the first config available, that is the best one, while on EGL it will iterate over them to select the one closest to what the application requested. Of course, anything lower than RGB888 looks bad and we really don’t want that.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 26 Mar 2015 20:20:37 +0100
parents 70e2ed71b09c
children d1f0bb0b7a17
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
292
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
1 # -*- encoding: utf-8 -*-
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
2 ##
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
3 ## Copyright (C) 2012 Thibaut Girka <thib@sitedethib.com>
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
4 ##
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
5 ## This program is free software; you can redistribute it and/or modify
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
6 ## it under the terms of the GNU General Public License as published
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
7 ## by the Free Software Foundation; version 3 only.
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
8 ##
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
9 ## This program is distributed in the hope that it will be useful,
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
12 ## GNU General Public License for more details.
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
13 ##
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
14
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
15
305
5492472963b0 Minor cleanup
Thibaut Girka <thib@sitedethib.com>
parents: 292
diff changeset
16 from struct import pack, unpack, Struct
292
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
17 from collections import namedtuple
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
18 from io import BytesIO
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
19
377
70e2ed71b09c Add meaningful exceptions in format parsing.
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
parents: 305
diff changeset
20 from pytouhou.formats import ChecksumError
70e2ed71b09c Add meaningful exceptions in format parsing.
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
parents: 305
diff changeset
21
292
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
22
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
23 class TH6Score(object):
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
24 entry_types = {
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
25 b'TH6K': (Struct('<I'),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
26 namedtuple('TH6K', ('unknown',))),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
27 b'HSCR': (Struct('<IIBBB8sx'),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
28 namedtuple('HSCR', ('unknown', 'score', 'character',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
29 'rank', 'stage', 'name'))),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
30 b'PSCR': (Struct('<IIBBBx'),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
31 namedtuple('PSCR', ('unknown', 'score', 'character',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
32 'rank', 'stage'))),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
33 b'CLRD': (Struct('<I5B5BBx'),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
34 namedtuple('CLRD', ('unknown',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
35 'easy', 'normal', 'hard', 'lunatic',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
36 'extra',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
37 'easy_continue', 'normal_continue',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
38 'hard_continue', 'lunatic_continue',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
39 'extra_continue',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
40 'character'))),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
41 b'CATK': (Struct('<I I HH I 34s H HH'),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
42 namedtuple('CATK', ('unknown', 'unknown2', 'num',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
43 'unknown3', 'padding',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
44 'name', 'padding2',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
45 'seen',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
46 'defeated'))),
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
47 }
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
48
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
49 def __init__(self):
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
50 self.key1 = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
51 self.key2 = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
52 self.unknown1 = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
53 self.unknown2 = 16
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
54 self.unknown3 = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
55 self.unknown4 = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
56 self.entries = []
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
57
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
58
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
59 @classmethod
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
60 def read(cls, file, decrypt=True, verify=True):
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
61 self = cls()
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
62
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
63 # Decrypt data
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
64 if decrypt:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
65 decrypted_file = BytesIO()
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
66 decrypted_file.write(file.read(1))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
67 key = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
68 for c in file.read():
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
69 encrypted = ord(c)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
70 key = ((key << 3) & 0xFF) | ((key >> 5) & 7)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
71 clear = encrypted ^ key
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
72 key += clear
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
73 decrypted_file.write(chr(clear))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
74 file = decrypted_file
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
75
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
76 # Read first-part header
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
77 file.seek(0)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
78 self.unknown1, self.key1, checksum = unpack('<BBH', file.read(4))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
79
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
80 # Verify checksum
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
81 if verify:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
82 #TODO: is there more to it?
377
70e2ed71b09c Add meaningful exceptions in format parsing.
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
parents: 305
diff changeset
83 real_sum = sum(ord(c) for c in file.read()) & 0xFFFF
70e2ed71b09c Add meaningful exceptions in format parsing.
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
parents: 305
diff changeset
84 if checksum != real_sum:
70e2ed71b09c Add meaningful exceptions in format parsing.
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
parents: 305
diff changeset
85 raise ChecksumError(checksum, real_sum)
292
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
86 file.seek(4)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
87
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
88 # Read second-part header
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
89 data = unpack('<HBBIII', file.read(16))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
90 self.unknown2, self.key2, self.unknown3, offset, self.unknown4, size = data
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
91
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
92 #TODO: verify size
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
93
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
94 # Read tags
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
95 file.seek(offset)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
96 while True:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
97 tag = file.read(4)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
98 if not tag:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
99 break
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
100 size, size2 = unpack('<HH', file.read(4))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
101 assert size == size2
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
102 assert size >= 8
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
103 data = file.read(size-8)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
104 data = cls.entry_types[tag][0].unpack(data)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
105 data = cls.entry_types[tag][1](*data)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
106 self.entries.append((tag, data))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
107
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
108 return self
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
109
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
110
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
111 def write(self, file, encrypt=True):
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
112 if encrypt:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
113 clearfile = BytesIO()
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
114 else:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
115 clearfile = file
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
116
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
117 # Write data
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
118 clearfile.seek(20)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
119 for entry in self.entries:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
120 #TODO
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
121 tag, data = entry
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
122 format = TH6Score.entry_types[tag][0]
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
123 clearfile.write(tag)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
124 clearfile.write(pack('<H', format.size + 8) * 2)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
125 clearfile.write(format.pack(*data))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
126
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
127 # Patch header
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
128 size = clearfile.tell()
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
129 clearfile.seek(0)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
130 clearfile.write(pack('<BBHHBBIII',
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
131 self.unknown1, self.key1, 0, self.unknown2,
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
132 self.key2, self.unknown3, 20, self.unknown4,
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
133 size))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
134
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
135 # Patch checksum
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
136 clearfile.seek(4)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
137 checksum = sum(ord(c) for c in clearfile.read()) & 0xFFFF
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
138 clearfile.seek(2)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
139 clearfile.write(pack('<H', checksum))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
140
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
141 # Encrypt
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
142 if encrypt:
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
143 clearfile.seek(0)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
144 file.write(clearfile.read(1))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
145 key = 0
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
146 for c in clearfile.read():
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
147 clear = ord(c)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
148 key = ((key << 3) & 0xFF) | ((key >> 5) & 7)
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
149 encrypted = clear ^ key
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
150 key += clear
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
151 file.write(chr(encrypted))
3a81b607f974 Add TH6 score.dat support.
Thibaut Girka <thib@sitedethib.com>
parents:
diff changeset
152