Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
MDPro3
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
SK
MDPro3
Commits
238ed70b
Commit
238ed70b
authored
Mar 19, 2026
by
SherryChaos
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use zero-width codec for online appearance sync
avoid disturbing other clients
parent
9aa76c97
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
129 additions
and
67 deletions
+129
-67
Assets/Scripts/MDPro3/Duel/OnlineAppearanceSync.cs
Assets/Scripts/MDPro3/Duel/OnlineAppearanceSync.cs
+127
-64
Assets/Scripts/MDPro3/Duel/TcpHelper.cs
Assets/Scripts/MDPro3/Duel/TcpHelper.cs
+0
-1
Assets/Scripts/MDPro3/Servant/RoomServant.cs
Assets/Scripts/MDPro3/Servant/RoomServant.cs
+2
-2
No files found.
Assets/Scripts/MDPro3/Duel/OnlineAppearanceSync.cs
View file @
238ed70b
using
MDPro3.Duel.YGOSharp
;
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
MDPro3.Duel.YGOSharp
;
using
System.Linq
;
using
System.Text
;
namespace
MDPro3
namespace
MDPro3
{
{
...
@@ -18,9 +20,6 @@ namespace MDPro3
...
@@ -18,9 +20,6 @@ namespace MDPro3
public
static
class
OnlineAppearanceSync
public
static
class
OnlineAppearanceSync
{
{
public
const
string
Prefix
=
"mdp3acc:v2:"
;
public
const
string
LegacyPrefix
=
"/mdp3acc:v1:"
;
private
const
int
DefaultCase
=
1080001
;
private
const
int
DefaultCase
=
1080001
;
private
const
int
DefaultProtector
=
1070001
;
private
const
int
DefaultProtector
=
1070001
;
private
const
int
DefaultField
=
1090001
;
private
const
int
DefaultField
=
1090001
;
...
@@ -38,7 +37,9 @@ namespace MDPro3
...
@@ -38,7 +37,9 @@ namespace MDPro3
public
static
string
BuildMessage
(
Deck
deck
)
public
static
string
BuildMessage
(
Deck
deck
)
{
{
var
data
=
BuildValidatedData
(
deck
);
var
data
=
BuildValidatedData
(
deck
);
return
$"
{
Prefix
}{
data
.
Case
}
,
{
data
.
Protector
}
,
{
data
.
Field
}
,
{
data
.
Grave
}
,
{
data
.
Stand
}
,
{
data
.
Mate
}
,
{
data
.
Face
}
,
{
data
.
Frame
}
"
;
var
ints
=
new
int
[]
{
data
.
Case
,
data
.
Protector
,
data
.
Field
,
data
.
Grave
,
data
.
Stand
,
data
.
Mate
,
data
.
Face
,
data
.
Frame
};
return
ZeroWidthIntsCodec
.
Encode
(
ints
);
}
}
public
static
string
BuildMessageForLocalPlayer
(
Deck
deck
)
public
static
string
BuildMessageForLocalPlayer
(
Deck
deck
)
...
@@ -53,68 +54,25 @@ namespace MDPro3
...
@@ -53,68 +54,25 @@ namespace MDPro3
if
(!
TryExtractPayload
(
content
,
out
var
payload
))
if
(!
TryExtractPayload
(
content
,
out
var
payload
))
return
false
;
return
false
;
var
parts
=
payload
.
Split
(
','
);
if
(
parts
.
Length
!=
6
&&
parts
.
Length
!=
8
)
return
false
;
if
(!
int
.
TryParse
(
parts
[
0
],
out
var
deckCase
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
1
],
out
var
protector
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
2
],
out
var
field
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
3
],
out
var
grave
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
4
],
out
var
stand
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
5
],
out
var
mate
))
return
false
;
var
defaultFace
=
GetDefaultCode
(
Items
.
ItemType
.
Face
,
DefaultFace
);
var
defaultFrame
=
GetDefaultCode
(
Items
.
ItemType
.
Frame
,
DefaultFrame
);
var
face
=
defaultFace
;
var
frame
=
defaultFrame
;
if
(
parts
.
Length
>=
8
)
{
if
(!
int
.
TryParse
(
parts
[
6
],
out
face
))
return
false
;
if
(!
int
.
TryParse
(
parts
[
7
],
out
frame
))
return
false
;
}
data
=
new
OnlineAppearanceData
data
=
new
OnlineAppearanceData
{
{
Case
=
EnsureValidCode
(
deckCase
,
Items
.
ItemType
.
Case
,
DefaultCase
),
Case
=
EnsureValidCode
(
payload
[
0
]
,
Items
.
ItemType
.
Case
,
DefaultCase
),
Protector
=
EnsureValidCode
(
p
rotector
,
Items
.
ItemType
.
Protector
,
DefaultProtector
),
Protector
=
EnsureValidCode
(
p
ayload
[
1
]
,
Items
.
ItemType
.
Protector
,
DefaultProtector
),
Field
=
EnsureValidCode
(
field
,
Items
.
ItemType
.
Mat
,
DefaultField
),
Field
=
EnsureValidCode
(
payload
[
2
]
,
Items
.
ItemType
.
Mat
,
DefaultField
),
Grave
=
EnsureValidCode
(
grave
,
Items
.
ItemType
.
Grave
,
DefaultGrave
),
Grave
=
EnsureValidCode
(
payload
[
3
]
,
Items
.
ItemType
.
Grave
,
DefaultGrave
),
Stand
=
EnsureValidCode
(
stand
,
Items
.
ItemType
.
Stand
,
DefaultStand
),
Stand
=
EnsureValidCode
(
payload
[
4
]
,
Items
.
ItemType
.
Stand
,
DefaultStand
),
Mate
=
EnsureValidCode
(
mate
,
Items
.
ItemType
.
Mate
,
DefaultMate
),
Mate
=
EnsureValidCode
(
payload
[
5
]
,
Items
.
ItemType
.
Mate
,
DefaultMate
),
Face
=
EnsureValidCode
(
face
,
Items
.
ItemType
.
Face
,
d
efaultFace
),
Face
=
EnsureValidCode
(
payload
[
6
],
Items
.
ItemType
.
Face
,
D
efaultFace
),
Frame
=
EnsureValidCode
(
frame
,
Items
.
ItemType
.
Frame
,
d
efaultFrame
),
Frame
=
EnsureValidCode
(
payload
[
7
],
Items
.
ItemType
.
Frame
,
D
efaultFrame
),
};
};
return
true
;
return
true
;
}
}
private
static
bool
TryExtractPayload
(
string
content
,
out
string
payload
)
private
static
bool
TryExtractPayload
(
string
content
,
out
int
[]
payload
)
{
{
payload
=
null
;
payload
=
ZeroWidthIntsCodec
.
Decode
(
content
);
if
(
string
.
IsNullOrEmpty
(
content
))
if
(
payload
==
null
)
return
false
;
return
false
;
return
true
;
if
(
content
.
StartsWith
(
Prefix
,
StringComparison
.
Ordinal
))
{
payload
=
content
.
Substring
(
Prefix
.
Length
).
Trim
().
TrimEnd
(
'\0'
);
return
true
;
}
if
(
content
.
StartsWith
(
LegacyPrefix
,
StringComparison
.
Ordinal
))
{
payload
=
content
.
Substring
(
LegacyPrefix
.
Length
).
Trim
().
TrimEnd
(
'\0'
);
return
true
;
}
return
false
;
}
}
public
static
bool
IsValid
(
OnlineAppearanceData
data
)
public
static
bool
IsValid
(
OnlineAppearanceData
data
)
...
@@ -255,11 +213,116 @@ namespace MDPro3
...
@@ -255,11 +213,116 @@ namespace MDPro3
if
(
list
==
null
)
if
(
list
==
null
)
return
false
;
return
false
;
for
(
var
i
=
0
;
i
<
list
.
Count
;
i
++)
return
list
.
Any
(
item
=>
item
.
id
==
id
);
if
(
list
[
i
].
id
==
id
)
}
return
true
;
}
public
static
class
ZeroWidthIntsCodec
{
// 定义4个零宽字符表示2比特
private
const
char
ZW_00
=
'\
u200B
'
;
// 零宽空格 -> 00
private
const
char
ZW_01
=
'\
u200C
'
;
// 零宽非连接符 -> 01
private
const
char
ZW_10
=
'\
u200D
'
;
// 零宽连接符 -> 10
private
const
char
ZW_11
=
'\
u200E
'
;
// 从左到右标记 -> 11
// 起始标记:两个连续ZW_10 (U+200D U+200D)
private
const
string
START_MARKER
=
"\u200D\u200D"
;
// 映射表:2比特值 -> 字符
private
static
readonly
char
[]
Bit2Char
=
new
char
[
4
]
{
ZW_00
,
ZW_01
,
ZW_10
,
ZW_11
};
// 反向映射:字符 -> 2比特值
private
static
readonly
Dictionary
<
char
,
byte
>
Char2Bit
=
new
()
{{
ZW_00
,
0
},
{
ZW_01
,
1
},
{
ZW_10
,
2
},
{
ZW_11
,
3
}};
public
static
string
Encode
(
int
[]
ints
)
{
if
(
ints
==
null
||
ints
.
Length
!=
8
)
throw
new
ArgumentException
(
"需要长度为8的int数组"
);
// 1. 将8个int转换为字节数组(小端序)
byte
[]
bytes
=
new
byte
[
32
];
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
byte
[]
intBytes
=
BitConverter
.
GetBytes
(
ints
[
i
]);
if
(!
BitConverter
.
IsLittleEndian
)
Array
.
Reverse
(
intBytes
);
Array
.
Copy
(
intBytes
,
0
,
bytes
,
i
*
4
,
4
);
}
// 2. 将字节数组转换为比特流(使用BitArray,索引0为最低位)
System
.
Collections
.
BitArray
bits
=
new
(
bytes
);
return
false
;
// 3. 每次取2比特,映射为字符
StringBuilder
sb
=
new
();
sb
.
Append
(
START_MARKER
);
for
(
int
i
=
0
;
i
<
bits
.
Length
;
i
+=
2
)
{
// 构建2比特值 (低位在前,即bit i为低位,bit i+1为高位)
int
value
=
(
bits
[
i
]
?
1
:
0
)
|
((
bits
[
i
+
1
]
?
1
:
0
)
<<
1
);
sb
.
Append
(
Bit2Char
[
value
]);
}
return
sb
.
ToString
();
}
public
static
int
[]
Decode
(
string
hiddenMessage
)
{
if
(
string
.
IsNullOrEmpty
(
hiddenMessage
))
return
null
;
// 查找起始标记
int
markerIndex
=
hiddenMessage
.
IndexOf
(
START_MARKER
,
StringComparison
.
Ordinal
);
if
(
markerIndex
==
-
1
)
return
null
;
string
dataPart
=
hiddenMessage
[(
markerIndex
+
START_MARKER
.
Length
)..];
// 收集比特
List
<
bool
>
bits
=
new
();
foreach
(
char
c
in
dataPart
)
{
if
(!
Char2Bit
.
TryGetValue
(
c
,
out
byte
twoBits
))
{
// 由于我们预期只有映射字符,所以如果出现未知字符,可视为无效消息。
return
null
;
}
// 将2比特拆分为两个布尔值,注意低位先存
bits
.
Add
((
twoBits
&
1
)
!=
0
);
// 低位
bits
.
Add
((
twoBits
&
2
)
!=
0
);
// 高位
}
// 校验比特数:应为256
if
(
bits
.
Count
!=
256
)
{
// 可能数据损坏
return
null
;
}
// 将比特数组转回字节数组
byte
[]
bytes
=
new
byte
[
32
];
for
(
int
i
=
0
;
i
<
bits
.
Count
;
i
++)
{
if
(
bits
[
i
])
{
int
byteIndex
=
i
/
8
;
int
bitIndex
=
i
%
8
;
bytes
[
byteIndex
]
|=
(
byte
)(
1
<<
bitIndex
);
}
}
// 解析8个int
int
[]
result
=
new
int
[
8
];
for
(
int
i
=
0
;
i
<
8
;
i
++)
{
byte
[]
intBytes
=
new
byte
[
4
];
Array
.
Copy
(
bytes
,
i
*
4
,
intBytes
,
0
,
4
);
if
(!
BitConverter
.
IsLittleEndian
)
Array
.
Reverse
(
intBytes
);
result
[
i
]
=
BitConverter
.
ToInt32
(
intBytes
,
0
);
}
return
result
;
}
}
}
}
}
}
Assets/Scripts/MDPro3/Duel/TcpHelper.cs
View file @
238ed70b
...
@@ -471,7 +471,6 @@ namespace MDPro3
...
@@ -471,7 +471,6 @@ namespace MDPro3
return
;
return
;
var
syncMessage
=
OnlineAppearanceSync
.
BuildMessageForLocalPlayer
(
deckFor
);
var
syncMessage
=
OnlineAppearanceSync
.
BuildMessageForLocalPlayer
(
deckFor
);
CtosMessage_Chat
(
syncMessage
);
CtosMessage_Chat
(
syncMessage
);
Debug
.
Log
(
$"[OnlineAppearance] Sent sync payload:
{
syncMessage
}
"
);
}
}
public
static
void
CtosMessage_UpdateAppearanceFromCurrentDeck
()
public
static
void
CtosMessage_UpdateAppearanceFromCurrentDeck
()
...
...
Assets/Scripts/MDPro3/Servant/RoomServant.cs
View file @
238ed70b
...
@@ -547,7 +547,7 @@ namespace MDPro3.Servant
...
@@ -547,7 +547,7 @@ namespace MDPro3.Servant
{
{
int
player
=
r
.
ReadInt16
();
int
player
=
r
.
ReadInt16
();
var
length
=
(
int
)((
r
.
BaseStream
.
Length
-
r
.
BaseStream
.
Position
)
/
2
);
var
length
=
(
int
)((
r
.
BaseStream
.
Length
-
r
.
BaseStream
.
Position
)
/
2
);
var
content
=
r
.
ReadUnicode
(
(
int
)
length
);
var
content
=
r
.
ReadUnicode
(
length
);
if
(
OnlineAppearanceSync
.
IsSyncMessage
(
content
))
if
(
OnlineAppearanceSync
.
IsSyncMessage
(
content
))
{
{
if
(
player
>=
0
&&
player
<
4
&&
OnlineAppearanceSync
.
TryParse
(
content
,
out
var
appearance
))
if
(
player
>=
0
&&
player
<
4
&&
OnlineAppearanceSync
.
TryParse
(
content
,
out
var
appearance
))
...
@@ -559,7 +559,7 @@ namespace MDPro3.Servant
...
@@ -559,7 +559,7 @@ namespace MDPro3.Servant
}
}
else
else
{
{
Debug
.
LogWarning
(
$"[OnlineAppearance] Ignored sync chat. seat=
{
player
}
, content='
{
content
}
'
"
);
Debug
.
LogWarning
(
$"[OnlineAppearance] Ignored sync chat. seat=
{
player
}
"
);
}
}
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