Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
N
Neos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
2
Merge Requests
2
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
Neos
Commits
7f078ab1
"...Irrlicht/svn:/svn.code.sf.net/p/irrlicht/code/trunk@3725" did not exist on "f80a8cd066483a7cfce51b2f4e6ccc5348ca13bf"
Commit
7f078ab1
authored
Feb 06, 2026
by
chechunchi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix match
parent
736537f0
Pipeline
#42889
passed with stages
in 2 minutes and 21 seconds
Changes
7
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
141 additions
and
43 deletions
+141
-43
neos.config.json
neos.config.json
+2
-2
neos.config.prod.json
neos.config.prod.json
+2
-2
src/api/mycard/index.ts
src/api/mycard/index.ts
+1
-0
src/api/mycard/match.ts
src/api/mycard/match.ts
+10
-2
src/api/mycard/room.ts
src/api/mycard/room.ts
+11
-8
src/api/mycard/u16Secret.ts
src/api/mycard/u16Secret.ts
+43
-0
src/ui/Match/index.tsx
src/ui/Match/index.tsx
+72
-29
No files found.
neos.config.json
View file @
7f078ab1
...
...
@@ -8,12 +8,12 @@
},
{
"name"
:
"mycard-athletic"
,
"ip"
:
"tiramisu.moe
cube
.com"
,
"ip"
:
"tiramisu.moe
next
.com"
,
"port"
:
"8912"
},
{
"name"
:
"mycard-custom"
,
"ip"
:
"tiramisu.moe
cube
.com"
,
"ip"
:
"tiramisu.moe
next
.com"
,
"port"
:
"7912"
},
{
...
...
neos.config.prod.json
View file @
7f078ab1
...
...
@@ -8,12 +8,12 @@
},
{
"name"
:
"mycard-athletic"
,
"ip"
:
"tiramisu.moe
cube
.com"
,
"ip"
:
"tiramisu.moe
next
.com"
,
"port"
:
"8912"
},
{
"name"
:
"mycard-custom"
,
"ip"
:
"tiramisu.moe
cube
.com"
,
"ip"
:
"tiramisu.moe
next
.com"
,
"port"
:
"7912"
},
{
...
...
src/api/mycard/index.ts
View file @
7f078ab1
...
...
@@ -3,4 +3,5 @@ export * from "./account";
export
*
from
"
./match
"
;
export
*
from
"
./options
"
;
export
*
from
"
./room
"
;
export
*
from
"
./u16Secret
"
;
export
*
from
"
./user
"
;
src/api/mycard/match.ts
View file @
7f078ab1
...
...
@@ -6,13 +6,21 @@ export interface MatchInfo {
password
:
string
;
}
/**
* 请求匹配
*
* @param username 用户名
* @param secret 认证密钥(优先使用 u16Secret,如果没有则使用 external_id)
* @param arena 匹配类型(athletic: 竞技, entertain: 娱乐)
* @returns 匹配信息(服务器地址、端口、密码)
*/
export
async
function
match
(
username
:
string
,
extraId
:
number
,
secret
:
number
,
arena
:
"
athletic
"
|
"
entertain
"
=
"
entertain
"
,
):
Promise
<
MatchInfo
|
undefined
>
{
const
headers
=
{
Authorization
:
"
Basic
"
+
customBase64Encode
(
username
+
"
:
"
+
extraId
),
Authorization
:
"
Basic
"
+
customBase64Encode
(
username
+
"
:
"
+
secret
),
};
let
response
:
Response
|
undefined
=
undefined
;
const
params
=
new
URLSearchParams
({
...
...
src/api/mycard/room.ts
View file @
7f078ab1
...
...
@@ -16,19 +16,20 @@ export interface Room {
options
:
Options
;
}
// 通过房间ID和
external_id
加密得出房间密码
// 通过房间ID和
secret
加密得出房间密码
//
// 用于加入MC服房间
// @param secret - 优先使用 u16Secret,如果没有则使用 external_id
export
function
getJoinRoomPasswd
(
roomID
:
string
,
external_id
:
number
,
secret
:
number
,
_private
:
boolean
=
false
,
):
string
{
const
optionsBuffer
=
new
Uint8Array
(
6
);
optionsBuffer
[
1
]
=
(
_private
?
RoomAction
.
JoinPrivate
:
RoomAction
.
JoinPublic
)
<<
4
;
encryptBuffer
(
optionsBuffer
,
external_id
);
encryptBuffer
(
optionsBuffer
,
secret
);
const
base64String
=
btoa
(
String
.
fromCharCode
(...
optionsBuffer
));
...
...
@@ -36,10 +37,11 @@ export function getJoinRoomPasswd(
}
// 获取创建房间的密码
// @param secret - 优先使用 u16Secret,如果没有则使用 external_id
export
function
getCreateRoomPasswd
(
options
:
Options
,
roomID
:
string
,
external_id
:
number
,
secret
:
number
,
_private
:
boolean
=
false
,
)
{
// ref: https://docs.google.com/document/d/1rvrCGIONua2KeRaYNjKBLqyG9uybs9ZI-AmzZKNftOI/edit
...
...
@@ -57,14 +59,15 @@ export function getCreateRoomPasswd(
writeUInt16LE
(
optionsBuffer
,
3
,
options
.
start_lp
);
optionsBuffer
[
5
]
=
(
options
.
start_hand
<<
4
)
|
options
.
draw_count
;
encryptBuffer
(
optionsBuffer
,
external_id
);
encryptBuffer
(
optionsBuffer
,
secret
);
const
base64String
=
btoa
(
String
.
fromCharCode
(...
optionsBuffer
));
return
base64String
+
roomID
;
}
// 填充校验码和加密
function
encryptBuffer
(
buffer
:
Uint8Array
,
external_id
:
number
)
{
// @param secret - 优先使用 u16Secret,如果没有则使用 external_id
function
encryptBuffer
(
buffer
:
Uint8Array
,
secret
:
number
)
{
let
checksum
=
0
;
for
(
let
i
=
1
;
i
<
buffer
.
length
;
i
++
)
{
...
...
@@ -73,11 +76,11 @@ function encryptBuffer(buffer: Uint8Array, external_id: number) {
buffer
[
0
]
=
checksum
&
0xff
;
const
secret
=
(
external_id
%
65535
)
+
1
;
const
encryptSecret
=
(
secret
%
65535
)
+
1
;
for
(
let
i
=
0
;
i
<
buffer
.
length
;
i
+=
2
)
{
const
value
=
readUInt16LE
(
buffer
,
i
);
const
xorResult
=
value
^
s
ecret
;
const
xorResult
=
value
^
encryptS
ecret
;
writeUInt16LE
(
buffer
,
i
,
xorResult
);
}
}
...
...
src/api/mycard/u16Secret.ts
0 → 100644
View file @
7f078ab1
/**
* 获取用户的 u16Secret
*
* u16Secret 是用于匹配和房间认证的时间轮换密钥
* 每次使用前都需要重新获取,因为它会按时间轮换
*/
const
API_URL
=
"
https://sapi.moecube.com:444/accounts/authUser
"
;
interface
U16SecretResponse
{
u16Secret
:
number
;
}
export
async
function
getUserU16Secret
(
token
:
string
):
Promise
<
number
>
{
if
(
!
token
)
{
throw
new
Error
(
"
获取用户密钥失败:token 不存在,请重新登录
"
);
}
try
{
const
response
=
await
fetch
(
API_URL
,
{
method
:
"
GET
"
,
headers
:
{
Authorization
:
`Bearer
${
token
}
`
,
},
});
if
(
!
response
.
ok
)
{
throw
new
Error
(
`HTTP
${
response
.
status
}
:
${
response
.
statusText
}
`
);
}
const
data
:
U16SecretResponse
=
await
response
.
json
();
if
(
data
.
u16Secret
===
null
||
data
.
u16Secret
===
undefined
)
{
throw
new
Error
(
"
服务器返回的数据中没有 u16Secret
"
);
}
return
data
.
u16Secret
;
}
catch
(
error
)
{
const
errorMsg
=
error
instanceof
Error
?
error
.
message
:
"
未知错误
"
;
console
.
error
(
"
获取 u16Secret 失败:
"
,
errorMsg
);
throw
new
Error
(
`获取用户密钥失败:
${
errorMsg
}
,请尝试重新登录`
);
}
}
src/ui/Match/index.tsx
View file @
7f078ab1
...
...
@@ -15,6 +15,7 @@ import {
getCreateRoomPasswd
,
getJoinRoomPasswd
,
getPrivateRoomID
,
getUserU16Secret
,
match
,
}
from
"
@/api
"
;
import
{
useConfig
}
from
"
@/config
"
;
...
...
@@ -61,11 +62,17 @@ export const Component: React.FC = () => {
const
onMatch
=
async
(
arena
:
"
athletic
"
|
"
entertain
"
)
=>
{
if
(
!
user
)
{
message
.
error
(
"
请先登录萌卡账号
"
);
}
else
{
return
;
}
try
{
arena
===
"
athletic
"
?
setAthleticMatchLoading
(
true
)
:
setEntertainMatchLoading
(
true
);
const
matchInfo
=
await
match
(
user
.
username
,
user
.
external_id
,
arena
);
// 每次匹配前都要重新获取 u16Secret,因为它会按时间轮换
const
u16Secret
=
await
getUserU16Secret
(
user
.
token
);
const
matchInfo
=
await
match
(
user
.
username
,
u16Secret
,
arena
);
if
(
matchInfo
)
{
await
connectSrvpro
({
...
...
@@ -75,7 +82,16 @@ export const Component: React.FC = () => {
});
}
else
{
message
.
error
(
"
匹配失败T_T
"
);
arena
===
"
athletic
"
?
setAthleticMatchLoading
(
false
)
:
setEntertainMatchLoading
(
false
);
}
}
catch
(
error
)
{
const
errorMsg
=
error
instanceof
Error
?
error
.
message
:
"
未知错误
"
;
message
.
error
(
errorMsg
);
arena
===
"
athletic
"
?
setAthleticMatchLoading
(
false
)
:
setEntertainMatchLoading
(
false
);
}
};
...
...
@@ -107,15 +123,21 @@ export const Component: React.FC = () => {
// 创建MC自定义房间
const
onCreateMCRoom
=
async
()
=>
{
if
(
user
)
{
if
(
!
user
)
{
return
;
}
try
{
const
mcServer
=
serverList
.
find
(
(
server
)
=>
server
.
name
===
"
mycard-custom
"
,
);
if
(
mcServer
)
{
// 每次操作前都要重新获取 u16Secret
const
u16Secret
=
await
getUserU16Secret
(
user
.
token
);
const
passWd
=
getCreateRoomPasswd
(
mcCustomRoomStore
.
options
,
String
(
getPrivateRoomID
(
user
.
external_id
)),
u
ser
.
external_id
,
u
16Secret
,
true
,
);
await
connectSrvpro
({
...
...
@@ -124,19 +146,32 @@ export const Component: React.FC = () => {
passWd
,
});
}
}
catch
(
error
)
{
const
errorMsg
=
error
instanceof
Error
?
error
.
message
:
"
未知错误
"
;
message
.
error
(
errorMsg
);
}
};
// 加入MC自定义房间
const
onJoinMCRoom
=
async
()
=>
{
if
(
user
)
{
if
(
mcCustomRoomStore
.
friendPrivateID
!==
undefined
)
{
if
(
!
user
)
{
return
;
}
if
(
mcCustomRoomStore
.
friendPrivateID
===
undefined
)
{
message
.
error
(
"
请输入朋友的私密房间密码!
"
);
return
;
}
try
{
const
mcServer
=
serverList
.
find
(
(
server
)
=>
server
.
name
===
"
mycard-custom
"
,
);
if
(
mcServer
)
{
// 每次操作前都要重新获取 u16Secret
const
u16Secret
=
await
getUserU16Secret
(
user
.
token
);
const
passWd
=
getJoinRoomPasswd
(
String
(
mcCustomRoomStore
.
friendPrivateID
),
user
.
external_id
,
u16Secret
,
true
,
);
await
connectSrvpro
({
...
...
@@ -145,9 +180,9 @@ export const Component: React.FC = () => {
passWd
,
});
}
}
else
{
message
.
error
(
"
请输入朋友的私密房间密码!
"
)
;
}
}
catch
(
error
)
{
const
errorMsg
=
error
instanceof
Error
?
error
.
message
:
"
未知错误
"
;
message
.
error
(
errorMsg
);
}
};
...
...
@@ -161,7 +196,12 @@ export const Component: React.FC = () => {
width
:
"
40vw
"
,
okText
:
i18n
(
"
EnterSpectatorMode
"
),
onOk
:
async
()
=>
{
if
(
watchStore
.
watchID
)
{
if
(
!
watchStore
.
watchID
)
{
message
.
error
(
`
${
i18n
(
"
PleaseSelectTheRoomToSpectate
"
)}
`
);
return
;
}
try
{
setWatchLoading
(
true
);
// 找到MC竞技匹配的Server
...
...
@@ -169,10 +209,9 @@ export const Component: React.FC = () => {
(
server
)
=>
server
.
name
===
"
mycard-athletic
"
,
);
if
(
mcServer
)
{
const
passWd
=
getJoinRoomPasswd
(
watchStore
.
watchID
,
user
.
external_id
,
);
// 每次操作前都要重新获取 u16Secret
const
u16Secret
=
await
getUserU16Secret
(
user
.
token
);
const
passWd
=
getJoinRoomPasswd
(
watchStore
.
watchID
,
u16Secret
);
await
connectSrvpro
({
ip
:
mcServer
.
ip
+
"
:
"
+
mcServer
.
port
,
player
:
user
.
username
,
...
...
@@ -182,9 +221,13 @@ export const Component: React.FC = () => {
message
.
error
(
"
Something unexpected happened, please contact <ccc@neos.moe> to fix
"
,
);
setWatchLoading
(
false
);
}
}
else
{
message
.
error
(
`
${
i18n
(
"
PleaseSelectTheRoomToSpectate
"
)}
`
);
}
catch
(
error
)
{
const
errorMsg
=
error
instanceof
Error
?
error
.
message
:
"
未知错误
"
;
message
.
error
(
errorMsg
);
setWatchLoading
(
false
);
}
},
centered
:
true
,
...
...
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