Basic dialogue support
This commit is contained in:
parent
5161c5427d
commit
8f33b58648
260
ff5reader.py
260
ff5reader.py
|
@ -210,12 +210,12 @@ Glyphs_JP_large = list(Glyphs_JP2) # Large glyphs are subtly different again
|
||||||
Glyphs_JP_large[0xE0:0xEB] = ['←','→','+','、', '◯', '『', 'F', '°C', '・', '(', ')']
|
Glyphs_JP_large[0xE0:0xEB] = ['←','→','+','、', '◯', '『', 'F', '°C', '・', '(', ')']
|
||||||
|
|
||||||
Glyphs_Kanji = (
|
Glyphs_Kanji = (
|
||||||
'王','行','力',' ', ' ',' ',' ',' ', '入','城',' ','士', ' ',' ',' ','父', # 0x000
|
'王','行','力','様', ' ',' ',' ',' ', '入','城',' ','士', ' ',' ',' ','父', # 0x000
|
||||||
'人','見',' ',' ', '大',' ',' ','何', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x010
|
'人','見',' ',' ', '大',' ',' ','何', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x010
|
||||||
' ','心','間',' ', '風',' ',' ',' ', ' ',' ',' ',' ', '火',' ',' ',' ', # 0x020
|
' ','心','間',' ', '風',' ',' ',' ', ' ',' ',' ',' ', '火',' ',' ',' ', # 0x020
|
||||||
' ',' ',' ',' ', ' ',' ',' ',' ', '地',' ',' ',' ', ' ',' ',' ',' ', # 0x030
|
' ',' ',' ',' ', ' ',' ',' ',' ', '地',' ',' ',' ', ' ',' ',' ',' ', # 0x030
|
||||||
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','一',' ',' ', # 0x040
|
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','一',' ',' ', # 0x040
|
||||||
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x050
|
' ',' ','神',' ', ' ',' ','殿',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x050
|
||||||
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x060
|
' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x060
|
||||||
' ',' ','階',' ', ' ',' ',' ',' ', ' ',' ','土',' ', ' ',' ',' ',' ', # 0x070
|
' ',' ','階',' ', ' ',' ',' ',' ', ' ',' ','土',' ', ' ',' ',' ',' ', # 0x070
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ class FF5Reader(QMainWindow):
|
||||||
generate_glyphs_large(ROM2, glyph_sprites_large, 0x03E800)
|
generate_glyphs_large(ROM2, glyph_sprites_large, 0x03E800)
|
||||||
generate_glyphs_large(ROM2, glyph_sprites_kanji, 0x1BD000, 0x1AA)
|
generate_glyphs_large(ROM2, glyph_sprites_kanji, 0x1BD000, 0x1AA)
|
||||||
global zone_names
|
global zone_names
|
||||||
zone_names = make_string_img_list_indirect(0x107000, 2, 0x100, start_str=0x270000, start_jp_str=0x107200, large=True)
|
zone_names = make_string_img_list(0x107000, 2, 0x100, start_str=0x270000, start_jp_str=0x107200, indirect=True, large=True)
|
||||||
items = make_string_img_list(0x111380, 9, 256)
|
items = make_string_img_list(0x111380, 9, 256)
|
||||||
magics = make_string_img_list(0x111C80, 6, 87)
|
magics = make_string_img_list(0x111C80, 6, 87)
|
||||||
more_magics = make_string_img_list(0x111E8A, 9, 73)
|
more_magics = make_string_img_list(0x111E8A, 9, 73)
|
||||||
|
@ -355,52 +355,14 @@ class FF5Reader(QMainWindow):
|
||||||
npc_layers[-1].append("0x{0:0{1}X}".format(val, z[1]*2))
|
npc_layers[-1].append("0x{0:0{1}X}".format(val, z[1]*2))
|
||||||
j += z[1]
|
j += z[1]
|
||||||
|
|
||||||
glyph_layout = QGridLayout()
|
dialogue = make_string_img_list(0x2013F0, 3, 0x500, start_jp=0x082220, len_jp=2, start_str=0x0, start_jp_str=0x0A0000, indirect=True, large=True) # start_str=0x210000
|
||||||
for i in range(len(glyph_sprites)):
|
|
||||||
item = glyph_sprites[i]
|
|
||||||
pixmap_scaled = item.scaled(item.size() * 4)
|
|
||||||
lab = QLabel()
|
|
||||||
lab.setPixmap(pixmap_scaled)
|
|
||||||
glyph_layout.addWidget(lab, i // 16, i % 16)
|
|
||||||
glyph_frame = QWidget()
|
|
||||||
glyph_frame.setLayout(glyph_layout)
|
|
||||||
|
|
||||||
glyph_layout2 = QGridLayout()
|
|
||||||
for i in range(len(glyph_sprites2)):
|
|
||||||
item = glyph_sprites2[i]
|
|
||||||
pixmap_scaled = item.scaled(item.size() * 4)
|
|
||||||
lab = QLabel()
|
|
||||||
lab.setPixmap(pixmap_scaled)
|
|
||||||
glyph_layout2.addWidget(lab, i // 16, i % 16)
|
|
||||||
glyph_frame2 = QWidget()
|
|
||||||
glyph_frame2.setLayout(glyph_layout2)
|
|
||||||
|
|
||||||
glyph_layout3 = QGridLayout()
|
|
||||||
for i in range(len(glyph_sprites_large)):
|
|
||||||
item = glyph_sprites_large[i]
|
|
||||||
pixmap_scaled = item.scaled(item.size() * 2)
|
|
||||||
lab = QLabel()
|
|
||||||
lab.setPixmap(pixmap_scaled)
|
|
||||||
glyph_layout3.addWidget(lab, i // 16, i % 16)
|
|
||||||
glyph_frame3 = QWidget()
|
|
||||||
glyph_frame3.setLayout(glyph_layout3)
|
|
||||||
|
|
||||||
glyph_layout4 = QGridLayout()
|
|
||||||
for i in range(len(glyph_sprites_kanji)):
|
|
||||||
item = glyph_sprites_kanji[i]
|
|
||||||
pixmap_scaled = item.scaled(item.size() * 2)
|
|
||||||
lab = QLabel()
|
|
||||||
lab.setPixmap(pixmap_scaled)
|
|
||||||
glyph_layout4.addWidget(lab, i // 16, i % 16)
|
|
||||||
glyph_frame4 = QWidget()
|
|
||||||
glyph_frame4.setLayout(glyph_layout4)
|
|
||||||
|
|
||||||
self.tabwidget = QTabWidget()
|
self.tabwidget = QTabWidget()
|
||||||
self.enemy_sprites = QFrame()
|
self.enemy_sprites = QFrame()
|
||||||
self.tabwidget.addTab(glyph_frame, "Glyphs (EN)")
|
self.tabwidget.addTab(make_pixmap_table(glyph_sprites, 4), "Glyphs (EN)")
|
||||||
self.tabwidget.addTab(glyph_frame2, "Glyphs (JP)")
|
self.tabwidget.addTab(make_pixmap_table(glyph_sprites2, 4), "Glyphs (JP)")
|
||||||
self.tabwidget.addTab(glyph_frame3, "Glyphs (Large)")
|
self.tabwidget.addTab(make_pixmap_table(glyph_sprites_large, 2), "Glyphs (Large)")
|
||||||
self.tabwidget.addTab(glyph_frame4, "Glyphs (Kanji)")
|
self.tabwidget.addTab(make_pixmap_table(glyph_sprites_kanji, 2), "Glyphs (Kanji)")
|
||||||
self.tabwidget.addTab(self.enemy_sprites, "Enemy Sprites")
|
self.tabwidget.addTab(self.enemy_sprites, "Enemy Sprites")
|
||||||
self.tabwidget.addTab(make_table(zone_headers, zone_data, True), "Zones")
|
self.tabwidget.addTab(make_table(zone_headers, zone_data, True), "Zones")
|
||||||
self.tabwidget.addTab(make_table(imglist_headers, zone_names, True), "Zone Names")
|
self.tabwidget.addTab(make_table(imglist_headers, zone_names, True), "Zone Names")
|
||||||
|
@ -409,6 +371,7 @@ class FF5Reader(QMainWindow):
|
||||||
self.tabwidget.addTab(make_table(imglist_headers, magics, row_labels=False), "Magics")
|
self.tabwidget.addTab(make_table(imglist_headers, magics, row_labels=False), "Magics")
|
||||||
self.tabwidget.addTab(make_table(imglist_headers, more_magics, row_labels=False), "More Magics")
|
self.tabwidget.addTab(make_table(imglist_headers, more_magics, row_labels=False), "More Magics")
|
||||||
self.tabwidget.addTab(make_table(imglist_headers, enemy_names, row_labels=False), "Enemy Names")
|
self.tabwidget.addTab(make_table(imglist_headers, enemy_names, row_labels=False), "Enemy Names")
|
||||||
|
self.tabwidget.addTab(make_table(imglist_headers, dialogue), "Dialogue")
|
||||||
|
|
||||||
layout = QHBoxLayout()
|
layout = QHBoxLayout()
|
||||||
layout.addWidget(self.tabwidget)
|
layout.addWidget(self.tabwidget)
|
||||||
|
@ -458,73 +421,145 @@ def make_string_img_large(bytestring):
|
||||||
if len(bytestring) < 1:
|
if len(bytestring) < 1:
|
||||||
raise ValueError('Empty bytestring was passed')
|
raise ValueError('Empty bytestring was passed')
|
||||||
string = ""
|
string = ""
|
||||||
img = QImage(len(bytestring)*16, 14, QImage.Format_RGB16)
|
rows = 20 # This is just for testing
|
||||||
|
img = QImage(len(bytestring)*16, 14*rows, QImage.Format_RGB16)
|
||||||
img.fill(bg_color)
|
img.fill(bg_color)
|
||||||
painter = QtGui.QPainter(img)
|
painter = QtGui.QPainter(img)
|
||||||
|
|
||||||
it = iter(range(len(bytestring)))
|
it = iter(range(len(bytestring)))
|
||||||
x = 0
|
x = 0
|
||||||
for i in it:
|
xmax = x
|
||||||
j = bytestring[i]
|
y = 0
|
||||||
if j < 0x1E:
|
tabstop = 0
|
||||||
string = string + '[0x{:04X}]'.format(int.from_bytes(bytestring[i:i+2],'little'))
|
try:
|
||||||
next(it)
|
for i in it:
|
||||||
elif j == 0x1E:
|
j = bytestring[i]
|
||||||
string = string + Glyphs_Kanji[bytestring[i+1]]
|
'''
|
||||||
painter.drawPixmap(x*16, 1, glyph_sprites_kanji[bytestring[i+1]])
|
There's a lot of dialogue substitutions, this is going to be annoying and/or messy.
|
||||||
next(it)
|
Is 0x00 a wait for input marker?
|
||||||
elif j == 0x1F:
|
0x03 is クリスタル
|
||||||
string = string + Glyphs_Kanji[0x100 + bytestring[i+1]]
|
0x0A is a speaker tab, perhaps linebreak as well?
|
||||||
painter.drawPixmap(x*16, 1, glyph_sprites_kanji[0x100 + bytestring[i+1]])
|
0x0D appears to expand to 風の神殿
|
||||||
next(it)
|
: (0xCF) appears to expand to って
|
||||||
else:
|
S (0xDD) to ……
|
||||||
string = string + Glyphs_JP_large[j]
|
C (0xDE) to だいじょうぶ
|
||||||
painter.drawPixmap(x*16, 1, glyph_sprites_large[j])
|
T (0xDF) to は、
|
||||||
x += 1
|
◯ (0xE4) appears to expand to して
|
||||||
|
F (0xE6) appears to expand to otousan (お父様)
|
||||||
|
・ (0xE8) appears to expand to です
|
||||||
|
% (0xCD) to !!
|
||||||
|
°C (0xE7) to !?
|
||||||
|
'''
|
||||||
|
if j == 0x01:
|
||||||
|
string = string + '\n[0x01]'
|
||||||
|
y += 1
|
||||||
|
xmax = x if x > xmax else xmax
|
||||||
|
x = 0
|
||||||
|
continue
|
||||||
|
elif j == 0x09:
|
||||||
|
string = string + '\t[0x09]'
|
||||||
|
x = tabstop
|
||||||
|
continue
|
||||||
|
elif j == 0x0A:
|
||||||
|
if x != 0:
|
||||||
|
y += 1
|
||||||
|
xmax = x if x > xmax else xmax
|
||||||
|
string = string + '\n'
|
||||||
|
string = string + '\t[0x0A]'
|
||||||
|
x = tabstop
|
||||||
|
continue
|
||||||
|
elif j == 0x19:
|
||||||
|
# Lenna is speaking
|
||||||
|
string = string + "[レナ「]"
|
||||||
|
painter.drawPixmap(0*16, (y*14)+1, glyph_sprites_large[0xAC])
|
||||||
|
painter.drawPixmap(1*16, (y*14)+1, glyph_sprites_large[0x92])
|
||||||
|
painter.drawPixmap(2*16, (y*14)+1, glyph_sprites_large[0xD0])
|
||||||
|
x += 3
|
||||||
|
tabstop = x
|
||||||
|
continue
|
||||||
|
elif j == 0x1B:
|
||||||
|
# Faris is speaking
|
||||||
|
string = string + "[ファリス「]"
|
||||||
|
painter.drawPixmap(0*16, (y*14)+1, glyph_sprites_large[0x64])
|
||||||
|
painter.drawPixmap(1*16, (y*14)+1, glyph_sprites_large[0xC4])
|
||||||
|
painter.drawPixmap(2*16, (y*14)+1, glyph_sprites_large[0xA8])
|
||||||
|
painter.drawPixmap(3*16, (y*14)+1, glyph_sprites_large[0x78])
|
||||||
|
painter.drawPixmap(4*16, (y*14)+1, glyph_sprites_large[0xD0])
|
||||||
|
x += 5
|
||||||
|
tabstop = x
|
||||||
|
continue
|
||||||
|
elif j < 0x1E:
|
||||||
|
string = string + '[0x{:02X}]'.format(j)
|
||||||
|
continue
|
||||||
|
elif j == 0x1E:
|
||||||
|
string = string + Glyphs_Kanji[bytestring[i+1]]
|
||||||
|
painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_kanji[bytestring[i+1]])
|
||||||
|
next(it)
|
||||||
|
elif j == 0x1F:
|
||||||
|
string = string + Glyphs_Kanji[0x100 + bytestring[i+1]]
|
||||||
|
painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_kanji[0x100 + bytestring[i+1]])
|
||||||
|
next(it)
|
||||||
|
else:
|
||||||
|
string = string + Glyphs_JP_large[j]
|
||||||
|
painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_large[j])
|
||||||
|
x += 1
|
||||||
|
if (j == 0xD0) or (j == 0xE5):
|
||||||
|
tabstop = x
|
||||||
|
except (StopIteration, IndexError):
|
||||||
|
pass
|
||||||
del painter
|
del painter
|
||||||
return string, QPixmap.fromImage(img.copy(0, 0, x*16, 14))
|
xmax = x if x > xmax else xmax
|
||||||
|
return string, QPixmap.fromImage(img.copy(0, 0, xmax*16, (y+1)*14))
|
||||||
|
|
||||||
def make_string_img_list(start, length, num, start_jp=None, len_jp=None, indirect=False, large=False):
|
def make_string_img_list(start, length, num, start_jp=None, len_jp=None, start_str=None, start_jp_str=None, indirect=False, large=False):
|
||||||
start_jp = start if start_jp is None else start_jp
|
|
||||||
len_jp = length if len_jp is None else len_jp
|
|
||||||
stringlist = []
|
|
||||||
id_digits = hex_length(num-1)
|
|
||||||
for id in range(num):
|
|
||||||
j1 = start + (id*length)
|
|
||||||
j2 = start_jp + (id*len_jp)
|
|
||||||
string, img = make_string_img(ROM[j1:j1+length])
|
|
||||||
if large:
|
|
||||||
string_JP, img_JP = make_string_img_large(ROM2[j2:j2+len_jp])
|
|
||||||
else:
|
|
||||||
string_JP, img_JP = make_string_img(ROM2[j2:j2+len_jp], jp=True)
|
|
||||||
stringlist.append(["0x{:06X}".format(j1), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP])
|
|
||||||
return stringlist
|
|
||||||
|
|
||||||
def make_string_img_list_indirect(start, length, num, start_jp=None, len_jp=None, start_str=None, start_jp_str=None, large=False):
|
|
||||||
start_jp = start if start_jp is None else start_jp
|
start_jp = start if start_jp is None else start_jp
|
||||||
len_jp = length if len_jp is None else len_jp
|
len_jp = length if len_jp is None else len_jp
|
||||||
start_str = start if start_str is None else start_str
|
start_str = start if start_str is None else start_str
|
||||||
start_jp_str = start_str if start_jp_str is None else start_jp_str
|
start_jp_str = start_str if start_jp_str is None else start_jp_str
|
||||||
stringlist = []
|
stringlist = []
|
||||||
id_digits = hex_length(num-1)
|
id_digits = hex_length(num-1)
|
||||||
for id in range(num):
|
if indirect:
|
||||||
en = start + (id*length)
|
for id in range(num):
|
||||||
jp = start_jp + (id*len_jp)
|
en = start + (id*length)
|
||||||
en_start = int.from_bytes(ROM[en:en+length],'little') + start_str
|
jp = start_jp + (id*len_jp)
|
||||||
en_end = int.from_bytes(ROM[en+length:en+(length*2)],'little') + start_str
|
en_start = int.from_bytes(ROM[en:en+length],'little') + start_str
|
||||||
jp_start = int.from_bytes(ROM2[jp:jp+len_jp],'little') + start_jp_str
|
en_end = int.from_bytes(ROM[en+length:en+(length*2)],'little') + start_str
|
||||||
jp_end = int.from_bytes(ROM2[jp+len_jp:jp+(len_jp*2)],'little') + start_jp_str
|
if en_start >= 0xC00000:
|
||||||
if (en_end == start_str) or (jp_end == start_jp_str) or (en_end > len(ROM)) or (jp_end > len(ROM2)):
|
en_start -= 0xC00000
|
||||||
break
|
en_end -= 0xC00000
|
||||||
try:
|
jp_start = int.from_bytes(ROM2[jp:jp+len_jp],'little') + start_jp_str
|
||||||
string, img = make_string_img(ROM[en_start:en_end])
|
jp_end = int.from_bytes(ROM2[jp+len_jp:jp+(len_jp*2)],'little') + start_jp_str
|
||||||
|
if (en_end == start_str) or (jp_end == start_jp_str) or (en_end > len(ROM)) or (jp_end > len(ROM2)):
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
if en_end > en_start:
|
||||||
|
string, img = make_string_img(ROM[en_start:en_end])
|
||||||
|
else:
|
||||||
|
string = ''
|
||||||
|
img = None
|
||||||
|
|
||||||
|
if jp_end > jp_start:
|
||||||
|
if large:
|
||||||
|
string_JP, img_JP = make_string_img_large(ROM2[jp_start:jp_end])
|
||||||
|
else:
|
||||||
|
string_JP, img_JP = make_string_img(ROM2[jp_start:jp_end], jp=True)
|
||||||
|
else:
|
||||||
|
string_JP = ''
|
||||||
|
img_JP = None
|
||||||
|
except ValueError:
|
||||||
|
print("ID: {} \tRef.0x{:06X} 0x{:06X} \tRange EN: 0x{:06X}-0x{:06X} \tRange JP: 0x{:06X}-0x{:06X}".format(id, en, jp, en_start, en_end, jp_start, jp_end))
|
||||||
|
raise
|
||||||
|
stringlist.append(["0x{:06X}".format(en), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP, "0x{:06X}".format(jp_start)])
|
||||||
|
else:
|
||||||
|
for id in range(num):
|
||||||
|
j1 = start + (id*length)
|
||||||
|
j2 = start_jp + (id*len_jp)
|
||||||
|
string, img = make_string_img(ROM[j1:j1+length])
|
||||||
if large:
|
if large:
|
||||||
string_JP, img_JP = make_string_img_large(ROM2[jp_start:jp_end])
|
string_JP, img_JP = make_string_img_large(ROM2[j2:j2+len_jp])
|
||||||
else:
|
else:
|
||||||
string_JP, img_JP = make_string_img(ROM2[jp_start:jp_end], jp=True)
|
string_JP, img_JP = make_string_img(ROM2[j2:j2+len_jp], jp=True)
|
||||||
except ValueError:
|
stringlist.append(["0x{:06X}".format(j1), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP])
|
||||||
print("ID: {} \tRef.0x{:06X} 0x{:06X} \tRange EN: 0x{:06X}-0x{:06X} \tRange JP: 0x{:06X}-0x{:06X}".format(id, en, jp, en_start, en_end, jp_start, jp_end))
|
|
||||||
raise
|
|
||||||
stringlist.append(["0x{:06X}".format(en), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP, "0x{:06X}".format(jp_start)])
|
|
||||||
return stringlist
|
return stringlist
|
||||||
|
|
||||||
def make_table(headers, items, sortable=False, row_labels=True):
|
def make_table(headers, items, sortable=False, row_labels=True):
|
||||||
|
@ -553,12 +588,39 @@ def make_table(headers, items, sortable=False, row_labels=True):
|
||||||
if item[:2] == "0x":
|
if item[:2] == "0x":
|
||||||
q_item.setFont(monofont)
|
q_item.setFont(monofont)
|
||||||
table.setItem(row, col, q_item)
|
table.setItem(row, col, q_item)
|
||||||
|
|
||||||
|
# Stupid hack to get table to size correctly
|
||||||
|
table.hide()
|
||||||
|
geometry = table.viewport().geometry()
|
||||||
|
table.viewport().setGeometry(QtCore.QRect(0, 0, 0x7FFFFFFF, 0x7FFFFFFF))
|
||||||
table.resizeColumnsToContents()
|
table.resizeColumnsToContents()
|
||||||
|
table.resizeRowsToContents()
|
||||||
|
table.viewport().setGeometry(geometry)
|
||||||
|
table.show()
|
||||||
|
|
||||||
if sortable:
|
if sortable:
|
||||||
table.setSortingEnabled(True)
|
table.setSortingEnabled(True)
|
||||||
table.sortItems(0)
|
table.sortItems(0)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
def make_pixmap_table(items, scale=4):
|
||||||
|
table = QTableWidget()
|
||||||
|
rows = divceil(len(items), 16)
|
||||||
|
rd = hex_length(rows-1)+1
|
||||||
|
cols = 16
|
||||||
|
table.setRowCount(rows)
|
||||||
|
table.setVerticalHeaderLabels(['0x{0:0{1}X}'.format(v*16, rd) for v in range(rows)])
|
||||||
|
table.setColumnCount(cols)
|
||||||
|
table.setHorizontalHeaderLabels(['0x{:X}'.format(v) for v in range(cols)])
|
||||||
|
for i in range(len(items)):
|
||||||
|
item = items[i]
|
||||||
|
pixmap_scaled = item.scaled(item.size() * scale)
|
||||||
|
lab = QLabel()
|
||||||
|
lab.setPixmap(pixmap_scaled)
|
||||||
|
table.setCellWidget(i // cols, i % cols, lab)
|
||||||
|
table.resizeColumnsToContents()
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
Loading…
Reference in New Issue