Skip to content

Commit

Permalink
Update to 0.4.3
Browse files Browse the repository at this point in the history
Add Link Play Support (Part2[Partial UDP Part Only]) (#10)

Co-Authored-By: Linwenxuan04 <[email protected]>
  • Loading branch information
Misaka12456 and Linwenxuan04 committed May 2, 2022
1 parent ef64cbb commit f549746
Show file tree
Hide file tree
Showing 16 changed files with 1,361 additions and 30 deletions.
10 changes: 10 additions & 0 deletions Arcaea Server 2.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{B676BE8F-4
docs\userinfo.md = docs\userinfo.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Team123it.Arcaea.MarveCube.LinkPlay", "Team123it.Arcaea.MarveCube.LinkPlay\Team123it.Arcaea.MarveCube.LinkPlay.csproj", "{8C0D9B41-31C4-40DD-A33D-43771F81554C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Expand All @@ -45,6 +47,14 @@ Global
{B8AD8196-3878-45D1-B3E3-1092B2A2DFC5}.Release|ARM64.Build.0 = Release|ARM64
{B8AD8196-3878-45D1-B3E3-1092B2A2DFC5}.Release|x64.ActiveCfg = Release|x64
{B8AD8196-3878-45D1-B3E3-1092B2A2DFC5}.Release|x64.Build.0 = Release|x64
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Debug|ARM64.Build.0 = Debug|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Debug|x64.Build.0 = Debug|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Release|ARM64.ActiveCfg = Release|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Release|ARM64.Build.0 = Release|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Release|x64.ActiveCfg = Release|Any CPU
{8C0D9B41-31C4-40DD-A33D-43771F81554C}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

* [Team123it.Arcaea.MarveCube](./Team123it.Arcaea.MarveCube) - Arcaea Server 2 主服务器后端
* [Team123it.Arcaea.MarveCube.Standalone](./Team123it.Arcaea.MarveCube.Standalone) - Arcaea Server 2 独立下载服务器后端
* [Team123it.Arcaea.MarveCube.LinkPlay](./Team123it.Arcaea.MarveCube.LinkPlay) - Arcaea Server 2 独立LinkPlay后端

##### 运行环境(主服务器程序与下载服务器均需要)

Expand Down
209 changes: 209 additions & 0 deletions Team123it.Arcaea.MarveCube.LinkPlay/Core/LinkPlayClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
using System.Text;


namespace Team123it.Arcaea.MarveCube.LinkPlay.Core
{
public static class BytesHelper
{
public static byte[] Ushort2Bytes(ushort integer) => BitConverter.GetBytes(integer);
public static byte[] Int2Bytes(int integer) => BitConverter.GetBytes(integer);
public static byte[] Uint2Bytes(uint integer) => BitConverter.GetBytes(integer);
public static byte[] Long2Bytes(long integer) => BitConverter.GetBytes(integer);
public static byte[] Ulong2Bytes(ulong integer) => BitConverter.GetBytes(integer);
public static ushort Bytes2Ushort(byte[] bytes) => BitConverter.ToUInt16(bytes);
public static int Bytes2Int(byte[] bytes) => BitConverter.ToInt32(bytes);
public static uint Bytes2Uint(byte[] bytes) => BitConverter.ToUInt32(bytes);
public static long Bytes2Long(byte[] bytes) => BitConverter.ToInt64(bytes);
public static ulong Bytes2Ulong(byte[] bytes) => BitConverter.ToUInt64(bytes);
}



public enum Difficulties
{
Empty = 0xff,
Past = 0,
Present = 1,
Future = 2,
Beyond = 3
}
public enum ClearTypes
{
Empty = 0,
TrackLost = 1,
NormalClear = 2,
FullRecall = 3,
PureMemory = 4,
EasyClear = 5,
HardClear = 6,
}

/// <summary>
/// Room的状态 用于<see cref="Room.RoomState"/>
/// </summary>
/// <list type="bullet">
/// <item>Locked - 房间锁定,在房间刚刚创建或者刚刚有人加入房间时短暂出现</item>
/// <item>Choosing - 房间正在选择歌曲</item>
/// <item>NotReady - 准备界面 还有人没准备好</item>
/// <item>CountDown - 准备界面 所有人都准备好了 开始倒计时</item>
/// <item>SyncLatency - 同步延迟(存疑)</item>
/// <item>CountingDown - 倒计时中(存疑)</item>
/// <item>Playing - 正在游玩</item>
/// <item>GameEnd - 游戏结束结算, 关门或者所有人跑路</item>
/// </list>
public enum RoomStates
{
Locked = 1,
Choosing = 2,

NotReady = 3,
CountDown = 4,

SyncLatency = 5,
CountingDown = 6,

Playing = 7,
GameEnd = 8,

}

/// <summary>
/// Player的状态 用于<see cref="Player.PlayerState"/>
/// </summary>
/// <item>Choosing - 正在选择歌曲</item>
/// <item>Downloading - 正在下载歌曲</item>
/// <item>NotReady - 准备界面 人没准备好</item>
/// <item>Ready - 准备界面 人准备好了</item>
/// <item>Syncing - 进入游戏 但是在显示技能前</item>
/// <item>Async - 在Syncing Stage 1.5s 后出现</item>
/// <item>Playing - 正在游玩</item>
/// <item>GameEnd - 游戏结束结算, 关门或者跑路</item>
public enum PlayerStates
{
Choosing = 1,

Downloading = 2,
NotReady = 3,
Ready = 4,

Syncing = 5,
Async = 6,

Playing = 7,
GameEnd = 8,
}

public struct Player
{
public Player(int init) { }
public ulong PlayerId { get; set; } = 0;
public byte[] PlayerName { get; set; } = Encoding.ASCII.GetBytes("ArcaeaTest");
public ulong Token { get; set; } = 0;

public int CharacterId { get; set; } = 0xff;
public int LastCharacterId { get; set; } = 0xff;
public uint IsCharacterUncapped { get; set; } = 0;

public Difficulties Difficulty { get; set; } = Difficulties.Empty;
public Difficulties LastDifficulty { get; set; } = Difficulties.Empty;
public uint Score { get; set; } = 0;
public uint LastScore { get; set; } = 0;
public uint Timer { get; set; } = 0;
public uint LastTimer { get; set; } = 0;
public ClearTypes ClearType { get; set; } = ClearTypes.Empty;
public ClearTypes LastClearType { get; set; } = ClearTypes.Empty;
public int BestScoreFlag { get; set; } = 0;
public int BestPlayerFlag { get; set; } = 0;
public int FinishFlag { get; set; } = 0;

public PlayerStates PlayerState { get; set; } = PlayerStates.Choosing;
public int DownloadPercent { get; set; } = 0;
public int OnlineState { get; set; } = 0;

public ulong LastTimestamp { get; set; } = 0;
public int ExtraCommandQueue { get; set; } = 0;
public byte[] SongUnlock{ get; set; } = new byte[512];

public int StartCommandCount { get; set; } = 0;

public void SetPlayerName(string playerName) { PlayerName = Encoding.UTF8.GetBytes(playerName)[..16]; }
}

public struct Room
{
public Room(int init) { }
public ulong RoomId { get; set; } = 0;
public string? RoomCode { get; set; } = "ARCAEA";

public uint CountDown { get; set; } = 0xffffffff;
public ulong Timestamp { get; set; } = 0;
public RoomStates RoomState { get; set; } = RoomStates.Locked;
public ushort SongIdx { get; set; } = 0xffff;
public ushort LastSongIdx { get; set; } = 0xffff;

public byte[] SongUnlock { get; set; } = new byte[512];

public ulong HostId { get; set; } = 0;
public Player[] Players { get; set; } = {new(), new(), new(), new()};
public int PlayerCount { get; set; } = 0;

public ushort Interval { get; set; } = 1000;
public ulong Times { get; set; } = 100;
public int RoundSwitch { get; set; } = 0;

public List<byte> CommandQueue { get; set; } = new();
public uint CommandQueueLength { get; set; } = 0;

public byte[] GetPlayerInfo()
{
var returnedBytes = new List<byte>();
foreach (var player in Players)
{
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(player.PlayerId));
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.CharacterId)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.IsCharacterUncapped)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes((int) player.Difficulty)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.Score));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.Timer));
returnedBytes.AddRange(BytesHelper.Uint2Bytes((uint) player.ClearType)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes((uint) player.PlayerState)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.DownloadPercent)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.OnlineState)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.StartCommandCount)[..1]);
returnedBytes.AddRange(player.PlayerName[..16]);
}
return returnedBytes.ToArray();
}

public byte[] GetPlayerLastScore()
{
if (LastSongIdx == 0xffff)
{
var emptyBytes = new List<byte>();
for (var i = 0; i < 4; i++) emptyBytes.AddRange(new byte[]{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
return emptyBytes.ToArray();
}

var returnedBytes = new List<byte>();
for (var i = 0; i < 4; ++i)
{
var player = Players[i];
if (player.PlayerId != 0)
{
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.LastCharacterId)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes((uint)player.LastDifficulty)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.LastScore));
returnedBytes.AddRange(BytesHelper.Uint2Bytes((uint)player.LastClearType)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.BestScoreFlag)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.BestPlayerFlag)[..1]);
}
else
{
returnedBytes.AddRange(new byte[]{0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}

}
return returnedBytes.ToArray();
}
}
}
90 changes: 90 additions & 0 deletions Team123it.Arcaea.MarveCube.LinkPlay/Core/LinkPlayConstructor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
namespace Team123it.Arcaea.MarveCube.LinkPlay.Core
{
public class LinkPlayConstructor
{
public static LinkPlayConstructor CreateInstance()
{
return new LinkPlayConstructor();
}

public static byte[] Command0C(Room room)
{
var returnedBytes = new List<byte>();
var packPrefix = new byte[] {0x06, 0x16, 0x0C, 0x09};
returnedBytes.AddRange(packPrefix);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.RoomId));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CommandQueueLength));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Players[0].LastTimestamp));
returnedBytes.AddRange(BytesHelper.Int2Bytes((int)room.RoomState)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CountDown));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Timestamp));
return returnedBytes.ToArray();
}

public static byte[] Command12(Room room, uint playerIndex)
{
var returnedBytes = new List<byte>();
var player = room.Players[playerIndex];
var packPrefix = new byte[] {0x06, 0x16, 0x12, 0x09};
returnedBytes.AddRange(packPrefix);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.RoomId));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CommandQueueLength));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(player.LastTimestamp));
returnedBytes.AddRange(BitConverter.GetBytes(playerIndex)[..1]);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(player.PlayerId));
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.CharacterId)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.IsCharacterUncapped)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes((int)player.Difficulty)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.Score));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(player.Timer));
returnedBytes.AddRange(BytesHelper.Int2Bytes((int)player.ClearType)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes((uint)player.PlayerState)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.DownloadPercent)[..1]);
returnedBytes.AddRange(BytesHelper.Int2Bytes(player.OnlineState)[..1]);
return returnedBytes.ToArray();
}

public static byte[] Command13(Room room)
{
var returnedBytes = new List<byte>();
var packPrefix = new byte[] {0x06, 0x16, 0x13, 0x09};
returnedBytes.AddRange(packPrefix);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.RoomId));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CommandQueueLength));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Players[0].LastTimestamp));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.HostId));
returnedBytes.AddRange(BytesHelper.Int2Bytes((int)room.RoomState)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CountDown));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Timestamp));
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.SongIdx));
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.Interval));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Times)[..7]);
returnedBytes.AddRange(room.GetPlayerLastScore());
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.LastSongIdx));
returnedBytes.AddRange(BytesHelper.Int2Bytes(room.RoundSwitch)[..1]);
return returnedBytes.ToArray();
}

public static byte[] Command15(Room room)
{
var returnedBytes = new List<byte>();
var packPrefix = new byte[] {0x06, 0x16, 0x15, 0x09};
returnedBytes.AddRange(packPrefix);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.RoomId));
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CommandQueueLength));
returnedBytes.AddRange(room.GetPlayerInfo());
returnedBytes.AddRange(room.SongUnlock);
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.HostId));
returnedBytes.AddRange(BytesHelper.Int2Bytes((int)room.RoomState)[..1]);
returnedBytes.AddRange(BytesHelper.Uint2Bytes(room.CountDown));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Timestamp));
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.SongIdx));
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.Interval));
returnedBytes.AddRange(BytesHelper.Ulong2Bytes(room.Times)[..7]);
returnedBytes.AddRange(room.GetPlayerLastScore());
returnedBytes.AddRange(BytesHelper.Ushort2Bytes(room.LastSongIdx));
returnedBytes.AddRange(BytesHelper.Int2Bytes(room.RoundSwitch)[..1]);
return returnedBytes.ToArray();
}
}
}
36 changes: 36 additions & 0 deletions Team123it.Arcaea.MarveCube.LinkPlay/Core/LinkPlayCrypto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Security.Cryptography;

namespace Team123it.Arcaea.MarveCube.LinkPlay.Core
{
public class LinkPlayCrypto
{
private static readonly byte[] DefaultKey = {0x11, 0x45, 0x14, 0x19, 0x19, 0x19, 0x18, 0x00, 0x11, 0x45, 0x14, 0x19, 0x19, 0x19, 0x18, 0x00};

public static byte[] EncryptPack(byte[] token, byte[] body)
{
var random = new Random();
var iv = new byte[12]; random.NextBytes(iv);
var pad = 16 - (body.Length % 16); // pkcs7 padding
var padding = Enumerable.Repeat((byte)pad, pad).ToArray(); var padded = body.Concat(padding).ToArray();
var cipher = new byte[body.Length+pad]; var authTag = new byte[12];
using var aes = new AesGcm(DefaultKey);
aes.Encrypt(iv, padded, cipher, authTag);
var returnBytes = token.Concat(iv).Concat(authTag).Concat(cipher);
return returnBytes.ToArray();
}

public static byte[] DecryptPack(byte[] data)
{
var iv = data[8..20];
var authTag = data[20..32];
var cipher = data[32..];
var decrypted = new byte[cipher.Length];
using var aes = new AesGcm(DefaultKey);
aes.Decrypt(iv, cipher, authTag, decrypted);

var pad = decrypted[^1]; // removal of pkcs7 padding
return decrypted.Take(decrypted.Length - pad).ToArray();
}
}
}

Loading

0 comments on commit f549746

Please sign in to comment.