2018-04-22 04:14:09 +05:30
|
|
|
#!/usr/bin/python3
|
2018-04-25 09:49:44 +05:30
|
|
|
import ctypes, sys
|
2018-04-21 16:09:25 +05:30
|
|
|
|
2018-04-22 18:32:32 +05:30
|
|
|
#--------------------------------------+
|
2018-04-25 09:49:44 +05:30
|
|
|
# Devil 1: PLD Base
|
2018-04-22 18:32:32 +05:30
|
|
|
#--------------------------------------+
|
|
|
|
|
2018-04-21 16:09:25 +05:30
|
|
|
class PldHeader(ctypes.Structure):
|
2018-04-25 07:16:10 +05:30
|
|
|
_pack_ = 1
|
2018-04-21 16:09:25 +05:30
|
|
|
_fields_ = [
|
|
|
|
("numOffset", ctypes.c_int),
|
2018-04-22 18:32:32 +05:30
|
|
|
("offsets", ctypes.POINTER(ctypes.c_int))
|
2018-04-21 16:09:25 +05:30
|
|
|
]
|
|
|
|
|
2018-04-22 16:09:08 +05:30
|
|
|
class Devil1PLD_FN(ctypes.Structure):
|
2018-04-21 16:09:25 +05:30
|
|
|
_fields_ = [
|
2018-04-22 18:32:32 +05:30
|
|
|
("getheader" , ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
|
|
|
ctypes.POINTER(PldHeader),
|
|
|
|
ctypes.c_char_p)),
|
|
|
|
("sizeofsector", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_int,
|
|
|
|
ctypes.POINTER(PldHeader),
|
|
|
|
ctypes.c_int)),
|
|
|
|
("printheader" , ctypes.CFUNCTYPE(None,
|
|
|
|
ctypes.POINTER(PldHeader)))
|
|
|
|
]
|
|
|
|
|
|
|
|
#--------------------------------------+
|
2018-04-25 09:49:44 +05:30
|
|
|
# Devil 1: TEX Base
|
2018-04-22 18:32:32 +05:30
|
|
|
#--------------------------------------+
|
|
|
|
|
|
|
|
class TexturePack(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 18:32:32 +05:30
|
|
|
_fields_ = [
|
|
|
|
("id", ctypes.c_char * 4), # fixed length 4, reverse order
|
|
|
|
("batchNumber", ctypes.c_int),
|
|
|
|
("firstBatchOffset", ctypes.c_int),
|
|
|
|
("unknownA", ctypes.c_int)
|
|
|
|
]
|
|
|
|
|
|
|
|
class TextureBatchDescriptor(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 18:32:32 +05:30
|
|
|
_fields_ = [
|
|
|
|
("batchIdx", ctypes.c_int),
|
|
|
|
("hash", ctypes.c_int),
|
|
|
|
("texNumber", ctypes.c_int),
|
|
|
|
("unknownA", ctypes.c_int * 8),
|
|
|
|
("textureSize", ctypes.c_int),
|
|
|
|
("unknownB", ctypes.c_int * 30)
|
|
|
|
]
|
|
|
|
|
|
|
|
class Texture(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 18:32:32 +05:30
|
|
|
_fields_ = [
|
|
|
|
("data", ctypes.c_ubyte)
|
|
|
|
]
|
|
|
|
|
|
|
|
class TextureBatch(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 18:32:32 +05:30
|
|
|
_fields_ = [
|
|
|
|
("batch", ctypes.POINTER(Texture))
|
2018-04-21 16:09:25 +05:30
|
|
|
]
|
|
|
|
|
2018-04-22 18:32:32 +05:30
|
|
|
class Devil1TEX_FN(ctypes.Structure):
|
|
|
|
_fields_ = [
|
|
|
|
("printheader", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(TexturePack))),
|
|
|
|
("printbatchdesc", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(TextureBatchDescriptor))),
|
2018-04-24 06:29:46 +05:30
|
|
|
("getheader", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
2018-04-24 16:38:52 +05:30
|
|
|
ctypes.POINTER(
|
|
|
|
ctypes.POINTER(TexturePack)),
|
2018-04-24 06:29:46 +05:30
|
|
|
ctypes.c_char_p)),
|
2018-04-22 18:32:32 +05:30
|
|
|
("getbatchdesc", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
2018-04-24 16:38:52 +05:30
|
|
|
ctypes.POINTER(
|
|
|
|
ctypes.POINTER(TextureBatchDescriptor)),
|
2018-04-22 18:32:32 +05:30
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p,
|
|
|
|
ctypes.c_uint)),
|
|
|
|
("getbatch", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
2018-04-24 16:38:52 +05:30
|
|
|
ctypes.POINTER(
|
|
|
|
ctypes.POINTER(TextureBatch)),
|
2018-04-22 18:32:32 +05:30
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p,
|
|
|
|
ctypes.c_uint)),
|
2018-04-27 17:55:26 +05:30
|
|
|
("gettextures", ctypes.CFUNCTYPE(
|
2018-04-22 18:32:32 +05:30
|
|
|
ctypes.c_bool,
|
|
|
|
ctypes.POINTER(Texture),
|
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p,
|
|
|
|
ctypes.c_uint))
|
|
|
|
]
|
|
|
|
|
|
|
|
#--------------------------------------+
|
2018-04-25 09:49:44 +05:30
|
|
|
# Devil 1: GEO Base
|
2018-04-22 18:32:32 +05:30
|
|
|
#--------------------------------------+
|
|
|
|
|
2018-04-22 19:18:19 +05:30
|
|
|
class Header(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("numMesh", ctypes.c_ubyte),
|
|
|
|
("unknownNumberB", ctypes.c_ubyte),
|
|
|
|
("unknownNumberC", ctypes.c_ubyte),
|
|
|
|
("unknownNumberD", ctypes.c_ubyte),
|
|
|
|
("padding", ctypes.c_int),
|
|
|
|
("unknownOffset", ctypes.c_ulonglong)
|
|
|
|
]
|
|
|
|
|
|
|
|
class MeshHeader(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("numBatch", ctypes.c_short),
|
|
|
|
("numVertex", ctypes.c_short),
|
|
|
|
("u", ctypes.c_uint),
|
|
|
|
("offsetBatches", ctypes.c_ulonglong),
|
|
|
|
("flags", ctypes.c_ulonglong)
|
|
|
|
]
|
|
|
|
|
|
|
|
class Coordinate(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("x", ctypes.c_float),
|
|
|
|
("y", ctypes.c_float),
|
|
|
|
("z", ctypes.c_float)
|
|
|
|
]
|
|
|
|
|
|
|
|
class UVs(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("u", ctypes.c_short),
|
|
|
|
("v", ctypes.c_short)
|
|
|
|
]
|
|
|
|
|
|
|
|
class BoneIndexes(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("indexes", ctypes.c_ubyte * 4),
|
|
|
|
]
|
|
|
|
|
|
|
|
class BoneWeights(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("weights", ctypes.c_short)
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class BatchData(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("numVertex", ctypes.c_short),
|
|
|
|
("uB", ctypes.c_short),
|
|
|
|
("padding", ctypes.c_uint),
|
|
|
|
("offsetPositions", ctypes.c_ulonglong),
|
|
|
|
("offsetNormals", ctypes.c_ulonglong),
|
|
|
|
("offsetUVs", ctypes.c_ulonglong),
|
|
|
|
("offsetBoneIndexes", ctypes.c_ulonglong),
|
|
|
|
("offsetBoneWeights", ctypes.c_ulonglong),
|
|
|
|
("offsets", ctypes.c_ulonglong)
|
|
|
|
]
|
|
|
|
|
|
|
|
class VertexData(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("positions", ctypes.POINTER(Coordinate)),
|
|
|
|
("normals", ctypes.POINTER(Coordinate)),
|
|
|
|
("u", ctypes.POINTER(UVs)),
|
|
|
|
("bi", ctypes.POINTER(BoneIndexes)),
|
|
|
|
("bw", ctypes.POINTER(BoneWeights))
|
|
|
|
]
|
|
|
|
|
|
|
|
class Batch(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("bd", ctypes.POINTER(BatchData)),
|
|
|
|
("vd", VertexData)
|
|
|
|
]
|
|
|
|
|
|
|
|
class Mesh(ctypes.Structure):
|
2018-04-25 07:45:39 +05:30
|
|
|
_pack_ = 1
|
2018-04-22 19:18:19 +05:30
|
|
|
_fields_ = [
|
|
|
|
("b", ctypes.POINTER(Batch))
|
|
|
|
]
|
|
|
|
|
|
|
|
class Devil1GEO_FN(ctypes.Structure):
|
|
|
|
_fields_ = [
|
|
|
|
("printheader", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(Header))),
|
|
|
|
("printmeshheader", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(MeshHeader))),
|
|
|
|
("printbatch", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(Batch))),
|
|
|
|
("printcoordinate", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(Coordinate))),
|
2018-04-24 06:29:46 +05:30
|
|
|
("getheader", ctypes.CFUNCTYPE(
|
|
|
|
None,
|
|
|
|
ctypes.POINTER(ctypes.POINTER(Header)),
|
|
|
|
ctypes.c_char_p)),
|
2018-04-22 19:18:19 +05:30
|
|
|
("getmeshheader", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
2018-04-24 16:28:51 +05:30
|
|
|
ctypes.POINTER(ctypes.POINTER(MeshHeader)),
|
2018-04-22 19:18:19 +05:30
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p)),
|
|
|
|
("getbatch", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
|
|
|
ctypes.POINTER(Batch),
|
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p)),
|
|
|
|
("getmesh", ctypes.CFUNCTYPE(
|
|
|
|
ctypes.c_bool,
|
|
|
|
ctypes.POINTER(Mesh),
|
|
|
|
ctypes.c_uint,
|
|
|
|
ctypes.c_char_p,
|
|
|
|
ctypes.c_uint))
|
|
|
|
]
|
|
|
|
|
2018-04-25 09:49:44 +05:30
|
|
|
#--------------------------------------+
|
|
|
|
# Python Objs
|
|
|
|
#--------------------------------------+
|
|
|
|
|
|
|
|
sharedlib = './lib3ddevil1.so'
|
|
|
|
libc = ctypes.cdll.LoadLibrary(sharedlib)
|
|
|
|
if not libc:
|
|
|
|
print("Couldn't load %s" % sharedlib)
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
print("\nlib3ddevil1 loaded.")
|
|
|
|
devil1pld = Devil1PLD_FN.in_dll(libc, "DEVIL1PLD")
|
|
|
|
devil1tex = Devil1TEX_FN.in_dll(libc, "DEVIL1TEX")
|
|
|
|
devil1geo = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO")
|
|
|
|
|
|
|
|
class PLDHeader:
|
2018-04-25 10:21:11 +05:30
|
|
|
def __init__(self, filedata = None):
|
2018-04-27 17:12:18 +05:30
|
|
|
# Store C Struct in order to call C functions
|
|
|
|
self.cstruct = PldHeader()
|
2018-04-25 10:21:11 +05:30
|
|
|
if filedata:
|
2018-04-27 17:12:18 +05:30
|
|
|
devil1pld.getheader(ctypes.byref(self.cstruct), filedata)
|
|
|
|
self.eof = len(filedata)
|
|
|
|
|
|
|
|
def show(self):
|
|
|
|
devil1pld.printheader(ctypes.byref(self.cstruct))
|
|
|
|
return
|
|
|
|
|
|
|
|
def getnumoffsets(self):
|
|
|
|
return self.cstruct.numOffsets
|
|
|
|
|
|
|
|
# return pythonic list of offsets
|
|
|
|
def getoffsets(self):
|
|
|
|
return self.cstruct.offsets[:self.cstruct.numOffset]
|
|
|
|
|
|
|
|
def sizeofsector(self, i):
|
|
|
|
ptr = ctypes.byref(self.cstruct)
|
|
|
|
return devil1pld.sizeofsector(ptr, i, self.eof)
|
|
|
|
|
|
|
|
class TEXturePack:
|
|
|
|
def __init__(self, filedata):
|
2018-04-27 17:14:55 +05:30
|
|
|
self.cstruct = ctypes.pointer(TexturePack())
|
|
|
|
devil1tex.getheader(ctypes.byref(self.cstruct), filedata)
|
2018-04-27 17:12:18 +05:30
|
|
|
return
|
|
|
|
|
|
|
|
def show(self):
|
2018-04-27 17:14:55 +05:30
|
|
|
devil1tex.printheader(self.cstruct)
|
2018-04-27 17:12:18 +05:30
|
|
|
|
|
|
|
def getbatchno(self):
|
2018-04-27 17:14:55 +05:30
|
|
|
return self.cstruct.contents.batchNumber
|
2018-04-27 17:12:18 +05:30
|
|
|
|
|
|
|
def getfirstbatchoffset(self):
|
2018-04-27 17:14:55 +05:30
|
|
|
return self.cstruct.contents.firstBatchOffset
|
2018-04-27 17:12:18 +05:30
|
|
|
|
|
|
|
class TEXtureBatchDescriptor:
|
|
|
|
def __init__(self, i, filedata):
|
2018-04-27 17:27:35 +05:30
|
|
|
self.cstruct = ctypes.pointer(TextureBatchDescriptor())
|
|
|
|
ptrofptr = ctypes.byref(self.cstruct)
|
|
|
|
if filedata:
|
2018-04-27 17:12:18 +05:30
|
|
|
devil1tex.getbatchdesc(ptrofptr, i, filedata, len(filedata))
|
|
|
|
return
|
|
|
|
|
2018-04-27 17:27:35 +05:30
|
|
|
def show(self):
|
|
|
|
devil1tex.printbatchdesc(self.cstruct)
|
2018-04-27 17:12:18 +05:30
|
|
|
|
2018-04-27 17:27:35 +05:30
|
|
|
def getbatchidx(self):
|
|
|
|
return self.cstruct.contents.batchIdx
|
2018-04-27 17:12:18 +05:30
|
|
|
|
2018-04-27 17:27:35 +05:30
|
|
|
def gethash(self):
|
|
|
|
return self.cstruct.contents.hash
|
2018-04-27 17:12:18 +05:30
|
|
|
|
2018-04-27 17:27:35 +05:30
|
|
|
def gettexno(self):
|
|
|
|
return self.cstruct.contents.texNumber
|
2018-04-27 17:12:18 +05:30
|
|
|
|
2018-04-27 17:27:35 +05:30
|
|
|
def gettexturesize(self):
|
|
|
|
return self.cstruct.contents.textureSize
|
2018-04-27 17:12:18 +05:30
|
|
|
|
2018-04-27 17:55:26 +05:30
|
|
|
|
|
|
|
# Needs testing / correction - gettextures will 'return by parameter'
|
|
|
|
# a dynamic array of textures. Need to be able to access multiple Texture()
|
2018-04-27 17:12:18 +05:30
|
|
|
class TEXtures:
|
2018-04-27 17:55:26 +05:30
|
|
|
def __init__(self, i, count, filedata):
|
|
|
|
self.cstruct = ctypes.byref(Texture())
|
2018-04-27 17:12:18 +05:30
|
|
|
if filedata:
|
|
|
|
devil1tex.gettextures(self.cstruct, i, filedata, len(filedata))
|
|
|
|
return
|
2018-04-25 09:49:44 +05:30
|
|
|
|
2018-04-22 18:32:32 +05:30
|
|
|
#--------------------------------------+
|
|
|
|
# Regular Python
|
|
|
|
#--------------------------------------+
|
2018-04-22 19:18:19 +05:30
|
|
|
if __name__ == "__main__":
|
|
|
|
def pldtest(devil1pld, pldheader):
|
|
|
|
with open("pl01.pld", "rb") as f:
|
|
|
|
data = f.read()
|
|
|
|
devil1pld.getheader(ctypes.byref(pldheader), data)
|
|
|
|
devil1pld.printheader(ctypes.byref(pldheader))
|
2018-04-27 17:27:35 +05:30
|
|
|
# for offset in pldheader.getoffsets():
|
|
|
|
# print(hex(offset))
|
2018-04-22 19:18:19 +05:30
|
|
|
|
|
|
|
def textest(devil1tex, texheader):
|
2018-04-27 17:27:35 +05:30
|
|
|
print("texture test")
|
2018-04-22 19:18:19 +05:30
|
|
|
with open("pl01.pld_1.txp", "rb") as f:
|
|
|
|
data = f.read()
|
2018-04-24 16:38:52 +05:30
|
|
|
# texheader = ctypes.cast(data, ctypes.POINTER(TexturePack))
|
|
|
|
th = ctypes.pointer(texheader)
|
|
|
|
devil1tex.getheader(ctypes.byref(th), data)
|
|
|
|
devil1tex.printheader(th)
|
|
|
|
batchdesc = TextureBatchDescriptor()
|
|
|
|
bd = ctypes.pointer(batchdesc)
|
|
|
|
print("\nbatch descriptor:")
|
|
|
|
devil1tex.getbatchdesc(ctypes.byref(bd), 1, data, len(data))
|
2018-04-27 17:27:35 +05:30
|
|
|
devil1tex.printbatchdesc(bd)
|
|
|
|
print(bd.contents.textureSize)
|
2018-04-22 19:18:19 +05:30
|
|
|
|
|
|
|
def geotest(devil1geo, geoheader):
|
2018-04-27 17:27:35 +05:30
|
|
|
print("geo test")
|
2018-04-22 19:18:19 +05:30
|
|
|
with open("pl00.pld_0", "rb") as f:
|
|
|
|
data = f.read()
|
2018-04-24 16:28:51 +05:30
|
|
|
# geoheader = ctypes.cast(data, ctypes.POINTER(Header))
|
|
|
|
gh = ctypes.pointer(geoheader)
|
|
|
|
devil1geo.getheader(ctypes.byref(gh), data)
|
2018-04-24 17:28:21 +05:30
|
|
|
devil1geo.printheader(gh)
|
2018-04-24 16:28:51 +05:30
|
|
|
meshheader = MeshHeader()
|
|
|
|
mh = ctypes.pointer(meshheader)
|
|
|
|
devil1geo.getmeshheader(ctypes.byref(mh), 1, data)
|
|
|
|
devil1geo.printmeshheader(mh)
|
2018-04-22 19:18:19 +05:30
|
|
|
|
|
|
|
def main():
|
|
|
|
sharedlib='./lib3ddevil1.so'
|
|
|
|
libc = ctypes.cdll.LoadLibrary(sharedlib)
|
|
|
|
if (not libc):
|
|
|
|
print("Couldn't load %s" % sharedlib)
|
|
|
|
return 1
|
|
|
|
print("OK")
|
|
|
|
|
|
|
|
pldfn = Devil1PLD_FN.in_dll(libc, "DEVIL1PLD")
|
|
|
|
pldh = PldHeader()
|
|
|
|
pldtest(pldfn, pldh)
|
|
|
|
|
|
|
|
texfn = Devil1TEX_FN.in_dll(libc, "DEVIL1TEX")
|
|
|
|
texh = TexturePack()
|
|
|
|
textest(texfn, texh)
|
|
|
|
|
|
|
|
geofn = Devil1GEO_FN.in_dll(libc, "DEVIL1GEO")
|
|
|
|
geoh = Header()
|
|
|
|
geotest(geofn, geoh)
|
2018-04-22 16:09:08 +05:30
|
|
|
|
2018-04-25 09:49:44 +05:30
|
|
|
def mainx():
|
|
|
|
with open("pl01.pld", "rb") as f:
|
|
|
|
data = f.read()
|
|
|
|
pld = PLDHeader(data)
|
2018-04-27 17:12:18 +05:30
|
|
|
pld.show()
|
2018-04-25 10:21:11 +05:30
|
|
|
pld2 = PLDHeader()
|
2018-04-27 17:12:18 +05:30
|
|
|
pld2.show()
|
|
|
|
|
|
|
|
with open("pl01.pld_1.txp", "rb") as f:
|
|
|
|
data = f.read()
|
|
|
|
txp = TEXturePack(data)
|
|
|
|
txp.show()
|
2018-04-27 17:14:55 +05:30
|
|
|
print(txp.getbatchno())
|
|
|
|
print(txp.getfirstbatchoffset())
|
2018-04-27 17:27:35 +05:30
|
|
|
tbd = TEXtureBatchDescriptor(1, data)
|
|
|
|
tbd.show()
|
|
|
|
print(tbd.gettexturesize())
|
2018-04-27 17:55:26 +05:30
|
|
|
tx = TEXtures(0, tbd.gettexno(), data)
|
2018-04-27 17:27:35 +05:30
|
|
|
|
2018-04-22 16:09:08 +05:30
|
|
|
#---------------------------------------+
|
2018-04-27 17:27:35 +05:30
|
|
|
main()
|
2018-04-25 09:49:44 +05:30
|
|
|
mainx()
|
2018-04-22 16:09:08 +05:30
|
|
|
|