Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
Mirai
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
Mirai
Commits
45d07243
Commit
45d07243
authored
Jan 01, 2020
by
Him188
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Android stuff
parent
82f1fda6
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
117 additions
and
29 deletions
+117
-29
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
...mmonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+6
-4
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
...moe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+74
-5
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
...tlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
+2
-1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
.../net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
+6
-2
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
...ai/qqandroid/network/protocol/packet/login/LoginPacket.kt
+12
-0
mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
.../jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
+7
-7
mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
+10
-10
No files found.
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
View file @
45d07243
...
@@ -7,20 +7,26 @@ import net.mamoe.mirai.data.AddFriendResult
...
@@ -7,20 +7,26 @@ import net.mamoe.mirai.data.AddFriendResult
import
net.mamoe.mirai.data.ImageLink
import
net.mamoe.mirai.data.ImageLink
import
net.mamoe.mirai.message.data.Image
import
net.mamoe.mirai.message.data.Image
import
net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import
net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import
net.mamoe.mirai.qqandroid.network.QQAndroidClient
import
net.mamoe.mirai.qqandroid.utils.Context
import
net.mamoe.mirai.utils.BotConfiguration
import
net.mamoe.mirai.utils.BotConfiguration
import
net.mamoe.mirai.utils.MiraiInternalAPI
import
net.mamoe.mirai.utils.MiraiInternalAPI
import
kotlin.coroutines.CoroutineContext
import
kotlin.coroutines.CoroutineContext
internal
expect
class
QQAndroidBot
(
internal
expect
class
QQAndroidBot
(
context
:
Context
,
account
:
BotAccount
,
account
:
BotAccount
,
configuration
:
BotConfiguration
configuration
:
BotConfiguration
)
:
QQAndroidBotBase
)
:
QQAndroidBotBase
@UseExperimental
(
MiraiInternalAPI
::
class
)
@UseExperimental
(
MiraiInternalAPI
::
class
)
internal
abstract
class
QQAndroidBotBase
constructor
(
internal
abstract
class
QQAndroidBotBase
constructor
(
context
:
Context
,
account
:
BotAccount
,
account
:
BotAccount
,
configuration
:
BotConfiguration
configuration
:
BotConfiguration
)
:
BotImpl
<
QQAndroidBotNetworkHandler
>(
account
,
configuration
)
{
)
:
BotImpl
<
QQAndroidBotNetworkHandler
>(
account
,
configuration
)
{
val
client
:
QQAndroidClient
=
QQAndroidClient
(
context
,
account
)
override
val
qqs
:
ContactList
<
QQ
>
override
val
qqs
:
ContactList
<
QQ
>
get
()
=
TODO
(
"not implemented"
)
get
()
=
TODO
(
"not implemented"
)
...
@@ -47,10 +53,6 @@ internal abstract class QQAndroidBotBase constructor(
...
@@ -47,10 +53,6 @@ internal abstract class QQAndroidBotBase constructor(
TODO
(
"not implemented"
)
TODO
(
"not implemented"
)
}
}
override
suspend
fun
login
()
{
TODO
(
"not implemented"
)
}
override
suspend
fun
Image
.
getLink
():
ImageLink
{
override
suspend
fun
Image
.
getLink
():
ImageLink
{
TODO
(
"not implemented"
)
TODO
(
"not implemented"
)
}
}
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
View file @
45d07243
package
net.mamoe.mirai.qqandroid.network
package
net.mamoe.mirai.qqandroid.network
import
kotlinx.coroutines.
CompletableJob
import
kotlinx.coroutines.
*
import
kotlinx.
coroutines.Job
import
kotlinx.
io.core.*
import
kotlinx.
coroutines.SupervisorJob
import
kotlinx.
io.pool.useInstance
import
net.mamoe.mirai.network.BotNetworkHandler
import
net.mamoe.mirai.network.BotNetworkHandler
import
net.mamoe.mirai.qqandroid.QQAndroidBot
import
net.mamoe.mirai.qqandroid.QQAndroidBot
import
net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
import
net.mamoe.mirai.utils.io.*
import
kotlin.coroutines.CoroutineContext
import
kotlin.coroutines.CoroutineContext
internal
class
QQAndroidBotNetworkHandler
(
override
val
bot
:
QQAndroidBot
)
:
BotNetworkHandler
()
{
internal
class
QQAndroidBotNetworkHandler
(
override
val
bot
:
QQAndroidBot
)
:
BotNetworkHandler
()
{
override
val
supervisor
:
CompletableJob
=
SupervisorJob
(
bot
.
coroutineContext
[
Job
])
override
val
supervisor
:
CompletableJob
=
SupervisorJob
(
bot
.
coroutineContext
[
Job
])
private
val
channel
:
PlatformDatagramChannel
=
PlatformDatagramChannel
(
"wtlogin.qq.com"
,
8000
)
override
suspend
fun
login
()
{
override
suspend
fun
login
()
{
TODO
(
"not implemented"
)
launch
{
processReceive
()
}
val
buffer
=
IoBuffer
.
Pool
.
borrow
()
buffer
.
writePacket
(
LoginPacket
(
bot
.
client
).
delegate
)
val
shouldBeSent
=
buffer
.
readRemaining
check
(
channel
.
send
(
buffer
)
==
shouldBeSent
)
{
"Buffer is not entirely sent. "
+
"Required sent length=$shouldBeSent, but after channel.send, "
+
"buffer remains ${buffer.readBytes().toUHexString()}"
}
buffer
.
release
(
IoBuffer
.
Pool
)
println
(
"Login sent"
)
}
private
suspend
fun
processReceive
()
{
while
(
channel
.
isOpen
)
{
val
buffer
=
IoBuffer
.
Pool
.
borrow
()
try
{
channel
.
read
(
buffer
)
// JVM: withContext(IO)
}
catch
(
e
:
ClosedChannelException
)
{
dispose
()
return
}
catch
(
e
:
ReadPacketInternalException
)
{
bot
.
logger
.
error
(
"Socket channel read failed: ${e.message}"
)
continue
}
catch
(
e
:
CancellationException
)
{
return
}
catch
(
e
:
Throwable
)
{
bot
.
logger
.
error
(
"Caught unexpected exceptions"
,
e
)
continue
}
finally
{
if
(!
buffer
.
canRead
()
||
buffer
.
readRemaining
==
0
)
{
//size==0
//bot.logger.debug("processReceive: Buffer cannot be read")
buffer
.
release
(
IoBuffer
.
Pool
)
continue
}
// sometimes exceptions are thrown without this `if` clause
}
//buffer.resetForRead()
launch
(
CoroutineName
(
"handleServerPacket"
))
{
// `.use`: Ensure that the packet is consumed **totally**
// so that all the buffers are released
ByteArrayPool
.
useInstance
{
val
length
=
buffer
.
readRemaining
-
1
buffer
.
readFully
(
it
,
0
,
length
)
buffer
.
resetForWrite
()
buffer
.
writeFully
(
it
,
0
,
length
)
}
ByteReadPacket
(
buffer
,
IoBuffer
.
Pool
).
use
{
input
->
try
{
input
.
debugPrint
(
"Received"
)
}
catch
(
e
:
Exception
)
{
bot
.
logger
.
error
(
e
)
}
}
}
}
}
}
override
suspend
fun
awaitDisconnection
()
{
override
suspend
fun
awaitDisconnection
()
{
TODO
()
while
(
true
)
{
delay
(
100
)
// TODO: 2019/12/31
}
}
override
fun
dispose
(
cause
:
Throwable
?)
{
println
(
"Closed"
)
super
.
dispose
(
cause
)
}
}
override
val
coroutineContext
:
CoroutineContext
=
bot
.
coroutineContext
override
val
coroutineContext
:
CoroutineContext
=
bot
.
coroutineContext
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
View file @
45d07243
...
@@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.network
...
@@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.network
import
kotlinx.io.core.toByteArray
import
kotlinx.io.core.toByteArray
import
net.mamoe.mirai.BotAccount
import
net.mamoe.mirai.BotAccount
import
net.mamoe.mirai.qqandroid.utils.*
import
net.mamoe.mirai.qqandroid.utils.*
import
net.mamoe.mirai.utils.io.hexToBytes
/*
/*
APP ID:
APP ID:
...
@@ -41,7 +42,7 @@ internal open class QQAndroidClient(
...
@@ -41,7 +42,7 @@ internal open class QQAndroidClient(
var
networkType
:
NetworkType
=
NetworkType
.
WIFI
var
networkType
:
NetworkType
=
NetworkType
.
WIFI
val
apkSignatureMd5
:
ByteArray
=
TODO
()
val
apkSignatureMd5
:
ByteArray
=
"A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D"
.
hexToBytes
()
/**
/**
* 协议版本?, 8.2.0 的为 8001
* 协议版本?, 8.2.0 的为 8001
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt
View file @
45d07243
...
@@ -96,6 +96,9 @@ fun BytePacketBuilder.t106(
...
@@ -96,6 +96,9 @@ fun BytePacketBuilder.t106(
loginType
:
LoginType
loginType
:
LoginType
)
{
)
{
writeShort
(
0
x106
)
writeShort
(
0
x106
)
passwordMd5
.
requireSize
(
16
)
tgtgtKey
.
requireSize
(
16
)
guid
?.
requireSize
(
16
)
writeShortLVPacket
{
writeShortLVPacket
{
encryptAndWrite
(
md5
(
passwordMd5
+
(
salt
.
takeIf
{
it
!=
0L
}
?:
uin
).
toInt
().
toByteArray
()))
{
encryptAndWrite
(
md5
(
passwordMd5
+
(
salt
.
takeIf
{
it
!=
0L
}
?:
uin
).
toInt
().
toByteArray
()))
{
...
@@ -132,7 +135,7 @@ fun BytePacketBuilder.t106(
...
@@ -132,7 +135,7 @@ fun BytePacketBuilder.t106(
writeInt
(
loginType
.
value
)
writeInt
(
loginType
.
value
)
writeShortLVByteArray
(
uinAccount
)
// TODO check if should be empty byte[]
writeShortLVByteArray
(
uinAccount
)
// TODO check if should be empty byte[]
}
}
}
shouldEqualsTo
98
}
}
}
fun
BytePacketBuilder
.
t116
(
fun
BytePacketBuilder
.
t116
(
...
@@ -620,7 +623,8 @@ fun BytePacketBuilder.t318(
...
@@ -620,7 +623,8 @@ fun BytePacketBuilder.t318(
private
fun
Boolean
.
toByte
():
Byte
=
if
(
this
)
1
else
0
private
fun
Boolean
.
toByte
():
Byte
=
if
(
this
)
1
else
0
private
fun
Boolean
.
toInt
():
Int
=
if
(
this
)
1
else
0
private
fun
Boolean
.
toInt
():
Int
=
if
(
this
)
1
else
0
private
infix
fun
Int
.
shouldEqualsTo
(
int
:
Int
)
=
require
(
this
==
int
)
private
infix
fun
Int
.
shouldEqualsTo
(
int
:
Int
)
=
require
(
this
==
int
)
{
"Required $int, but found $this"
}
private
fun
ByteArray
.
requireSize
(
exactSize
:
Int
)
=
require
(
this
.
size
==
exactSize
)
{
"Required size $exactSize, but found ${this.size}"
}
fun
randomAndroidId
():
String
=
buildString
(
15
)
{
fun
randomAndroidId
():
String
=
buildString
(
15
)
{
repeat
(
15
)
{
append
(
Random
.
nextInt
(
10
))
}
repeat
(
15
)
{
append
(
Random
.
nextInt
(
10
))
}
...
...
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
View file @
45d07243
...
@@ -21,6 +21,9 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
...
@@ -21,6 +21,9 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray {
@UseExperimental
(
ExperimentalUnsignedTypes
::
class
)
@UseExperimental
(
ExperimentalUnsignedTypes
::
class
)
internal
object
LoginPacket
:
PacketFactory
<
LoginPacket
.
LoginPacketResponse
,
LoginPacketDecrypter
>(
LoginPacketDecrypter
)
{
internal
object
LoginPacket
:
PacketFactory
<
LoginPacket
.
LoginPacketResponse
,
LoginPacketDecrypter
>(
LoginPacketDecrypter
)
{
init
{
this
.
_id
=
PacketId
(
0
x0810
,
9
)
}
operator
fun
invoke
(
operator
fun
invoke
(
client
:
QQAndroidClient
client
:
QQAndroidClient
...
@@ -144,6 +147,15 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
...
@@ -144,6 +147,15 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
}
}
}
}
@Suppress
(
"FunctionName"
)
fun
PacketId
(
commandId
:
Int
,
subCommandId
:
Int
)
=
object
:
PacketId
{
override
val
commandId
:
Int
get
()
=
commandId
override
val
subCommandId
:
Int
get
()
=
subCommandId
}
interface
PacketId
{
interface
PacketId
{
val
commandId
:
Int
// ushort actually
val
commandId
:
Int
// ushort actually
val
subCommandId
:
Int
// ushort actually
val
subCommandId
:
Int
// ushort actually
...
...
mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt
View file @
45d07243
package
net.mamoe.mirai.qqandroid
package
net.mamoe.mirai.qqandroid
import
net.mamoe.mirai.BotAccount
import
net.mamoe.mirai.BotAccount
import
net.mamoe.mirai.alsoLogin
import
net.mamoe.mirai.qqandroid.utils.Context
import
net.mamoe.mirai.qqandroid.utils.ContextImpl
import
net.mamoe.mirai.utils.BotConfiguration
import
net.mamoe.mirai.utils.BotConfiguration
@Suppress
(
"FunctionName"
)
internal
fun
QQAndroidBot
(
account
:
BotAccount
,
configuration
:
BotConfiguration
)
=
QQAndroidBot
(
ContextImpl
(),
account
,
configuration
)
internal
actual
class
QQAndroidBot
actual
constructor
(
internal
actual
class
QQAndroidBot
actual
constructor
(
context
:
Context
,
account
:
BotAccount
,
account
:
BotAccount
,
configuration
:
BotConfiguration
configuration
:
BotConfiguration
)
:
QQAndroidBotBase
(
account
,
configuration
)
)
:
QQAndroidBotBase
(
context
,
account
,
configuration
)
\ No newline at end of file
suspend
fun
main
()
{
val
bot
=
QQAndroidBot
(
BotAccount
(
1
,
""
),
BotConfiguration
()).
alsoLogin
()
bot
.
network
.
awaitDisconnection
()
}
\ No newline at end of file
mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt
View file @
45d07243
...
@@ -16,13 +16,14 @@ import kotlinx.serialization.internal.ArrayListSerializer
...
@@ -16,13 +16,14 @@ import kotlinx.serialization.internal.ArrayListSerializer
import
kotlinx.serialization.json.Json
import
kotlinx.serialization.json.Json
import
net.mamoe.mirai.Bot
import
net.mamoe.mirai.Bot
import
net.mamoe.mirai.network.BotNetworkHandler
import
net.mamoe.mirai.network.BotNetworkHandler
import
net.mamoe.mirai.timpc.TIMPC
import
net.mamoe.mirai.timpc.TIMPC
import
net.mamoe.mirai.timpc.network.TIMProtocol
import
net.mamoe.mirai.timpc.network.TIMProtocol
import
net.mamoe.mirai.timpc.network.packet.*
import
net.mamoe.mirai.timpc.network.packet.*
import
net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket
import
net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
import
net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket
import
net.mamoe.mirai.timpc.network.packet.login.*
import
net.mamoe.mirai.timpc.network.packet.login.CaptchaKey
import
net.mamoe.mirai.timpc.network.packet.login.HeartbeatPacket
import
net.mamoe.mirai.timpc.network.packet.login.ShareKey
import
net.mamoe.mirai.timpc.network.packet.login.TouchKey
import
net.mamoe.mirai.utils.cryptor.Decrypter
import
net.mamoe.mirai.utils.cryptor.Decrypter
import
net.mamoe.mirai.utils.cryptor.DecryptionFailedException
import
net.mamoe.mirai.utils.cryptor.DecryptionFailedException
import
net.mamoe.mirai.utils.cryptor.NoDecrypter
import
net.mamoe.mirai.utils.cryptor.NoDecrypter
...
@@ -127,7 +128,7 @@ suspend fun main() {
...
@@ -127,7 +128,7 @@ suspend fun main() {
listenDevice
(
localIp
,
it
)
listenDevice
(
localIp
,
it
)
}
}
println
(
"Using sessionKey = ${sessionKey.value.toUHexString()}"
)
println
(
"Using sessionKey = ${sessionKey.value.toUHexString()}"
)
println
(
"Filter QQ = $
{qq?.toLong()}
"
)
println
(
"Filter QQ = $
qq
"
)
PacketDebugger
.
recorder
?.
let
{
println
(
"Recorder is enabled"
)
}
PacketDebugger
.
recorder
?.
let
{
println
(
"Recorder is enabled"
)
}
Runtime
.
getRuntime
().
addShutdownHook
(
thread
(
false
)
{
Runtime
.
getRuntime
().
addShutdownHook
(
thread
(
false
)
{
PacketDebugger
.
recorder
?.
writeTo
(
File
(
GMTDate
().
toString
()
+
".record"
))
?.
also
{
println
(
"${PacketDebugger.recorder.list.size} records saved."
)
}
PacketDebugger
.
recorder
?.
writeTo
(
File
(
GMTDate
().
toString
()
+
".record"
))
?.
also
{
println
(
"${PacketDebugger.recorder.list.size} records saved."
)
}
...
@@ -183,8 +184,7 @@ internal object PacketDebugger {
...
@@ -183,8 +184,7 @@ internal object PacketDebugger {
* 7. 运行完 `mov eax,dword ptr ss:[ebp+10]`
* 7. 运行完 `mov eax,dword ptr ss:[ebp+10]`
* 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey`
* 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey`
*/
*/
val
sessionKey
:
SessionKey
=
val
sessionKey
:
SessionKey
get
()
=
SessionKey
(
"D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC"
.
hexToBytes
())
SessionKey
(
"D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC"
.
hexToBytes
())
// TODO: 2019/12/7 无法访问 internal 是 kotlin bug, KT-34849
// TODO: 2019/12/7 无法访问 internal 是 kotlin bug, KT-34849
/**
/**
...
@@ -197,9 +197,9 @@ internal object PacketDebugger {
...
@@ -197,9 +197,9 @@ internal object PacketDebugger {
val
recorder
:
Recorder
?
=
Recorder
()
val
recorder
:
Recorder
?
=
Recorder
()
val
IgnoredPacketIdList
:
List
<
PacketId
>
=
listOf
(
val
IgnoredPacketIdList
:
List
<
PacketId
>
=
listOf
(
KnownPacketId
.
get
<
FriendOnlineStatusChangedPacket
>(),
//
KnownPacketId.get<FriendOnlineStatusChangedPacket>(),
KnownPacketId
.
get
<
ChangeOnlineStatusPacket
>(),
//
KnownPacketId.get<ChangeOnlineStatusPacket>(),
KnownPacketId
.
get
<
HeartbeatPacket
>()
//
KnownPacketId.get<HeartbeatPacket>()
)
)
suspend
fun
dataReceived
(
data
:
ByteArray
)
{
suspend
fun
dataReceived
(
data
:
ByteArray
)
{
...
@@ -304,7 +304,7 @@ internal object PacketDebugger {
...
@@ -304,7 +304,7 @@ internal object PacketDebugger {
// 3E 03 3F A2 02 00 00 00 01 2E 01 00 00 69 35
// 3E 03 3F A2 02 00 00 00 01 2E 01 00 00 69 35
discardExact
(
3
)
//head
discardExact
(
3
)
//head
val
id
=
net
.
mamoe
.
mirai
.
timpc
.
network
.
packet
.
matchPacketId
(
readUShort
())
val
id
=
matchPacketId
(
readUShort
())
val
sequence
=
readUShort
().
toUHexString
()
val
sequence
=
readUShort
().
toUHexString
()
if
(
IgnoredPacketIdList
.
contains
(
id
))
{
if
(
IgnoredPacketIdList
.
contains
(
id
))
{
return
return
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment