Init enter game

This commit is contained in:
Naruse
2025-06-14 11:15:32 +08:00
commit 6a03b39f07
568 changed files with 92872 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
using KianaBH.Configuration;
using KianaBH.Internationalization;
using Newtonsoft.Json;
using KianaBH.Util.Extensions;
namespace KianaBH.Util;
public static class ConfigManager
{
public static readonly Logger Logger = new("ConfigManager");
public static ConfigContainer Config { get; private set; } = new();
private static readonly string ConfigFilePath = Config.Path.ConfigPath + "/Config.json";
public static HotfixContainer Hotfix { get; private set; } = new();
private static readonly string HotfixFilePath = Config.Path.ConfigPath + "/Hotfix.json";
public static void LoadConfig()
{
LoadConfigData();
LoadHotfixData();
}
private static void LoadConfigData()
{
var file = new FileInfo(ConfigFilePath);
if (!file.Exists)
{
Config = new()
{
ServerOption =
{
Language = Extensions.Extensions.GetCurrentLanguage()
}
};
Logger.Info("Current Language is " + Config.ServerOption.Language);
SaveData(Config, ConfigFilePath);
}
using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(stream))
{
var json = reader.ReadToEnd();
Config = JsonConvert.DeserializeObject<ConfigContainer>(json)!;
}
SaveData(Config, ConfigFilePath);
}
private static void LoadHotfixData()
{
var file = new FileInfo(HotfixFilePath);
// Generate all necessary versions
var verList = Extensions.Extensions.GetSupportVersions();
Logger.Info(I18NManager.Translate("Server.ServerInfo.CurrentVersion",
verList.Aggregate((current, next) => $"{current}, {next}")));
if (!file.Exists)
{
Hotfix = new HotfixContainer();
SaveData(Hotfix, HotfixFilePath);
file.Refresh();
}
using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(stream))
{
var json = reader.ReadToEnd();
Hotfix = JsonConvert.DeserializeObject<HotfixContainer>(json)!;
}
foreach (var version in verList)
if (!Hotfix.Hotfixes.TryGetValue(version, out var _))
Hotfix.Hotfixes[version] = new();
SaveData(Hotfix, HotfixFilePath);
}
private static void SaveData(object data, string path)
{
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
using var writer = new StreamWriter(stream);
writer.Write(json);
}
public static void InitDirectories()
{
foreach (var property in Config.Path.GetType().GetProperties())
{
var dir = property.GetValue(Config.Path)?.ToString();
if (!string.IsNullOrEmpty(dir))
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace KianaBH.Util.Crypto;
public static class DispatchEncryption
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public static string? EncryptDispatchContent(string version, object? data)
{
if (!ConfigManager.Hotfix.AesKeys.TryGetValue(version, out var aesKey))
return null;
var serializedData = JsonSerializer.Serialize(data, JsonSerializerOptions);
var keyBytes = aesKey.Split(' ')
.Select(b => Convert.ToByte(b, 16))
.ToArray();
using var aes = Aes.Create();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.Key = keyBytes;
var encryptor = aes.CreateEncryptor();
var dataBytes = Encoding.UTF8.GetBytes(serializedData);
var encryptedBytes = encryptor.TransformFinalBlock(dataBytes, 0, dataBytes.Length);
return Convert.ToBase64String(encryptedBytes);
}
}

View File

@@ -0,0 +1,232 @@
using KianaBH.Proto;
using Newtonsoft.Json;
using System.Buffers.Binary;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace KianaBH.Util.Extensions;
public static partial class Extensions
{
#region Regex
[GeneratedRegex(@"CN|OS|BETA|PROD|CECREATION|Android|Win|iOS")]
public static partial Regex VersionRegex();
[GeneratedRegex(@"(?<=Avatar_)(.*?)(?=_Config)")]
public static partial Regex AvatarConfigRegex();
[GeneratedRegex(@"(?<=Avatar_RogueBattleevent)(.*?)(?=_Config.json)")]
public static partial Regex BattleEventDataRegex();
[GeneratedRegex(@"coin(\d+)tier")]
public static partial Regex ProductRegex();
#endregion
public static string GetCurrentLanguage()
{
var uiCulture = CultureInfo.CurrentUICulture;
return uiCulture.Name switch
{
"zh-CN" => "CHS",
"zh-TW" => "CHT",
"ja-JP" => "JP",
_ => "EN"
};
}
public static List<string> GetSupportVersions()
{
var verList = new List<string>();
if (GameConstants.GAME_VERSION[^1] == '5')
for (var i = 1; i < 6; i++)
verList.Add(GameConstants.GAME_VERSION + i.ToString());
else
verList.Add(GameConstants.GAME_VERSION);
return verList;
}
public static T RandomElement<T>(this List<T> values)
{
var index = new Random().Next(values.Count);
return values[index];
}
public static string RandomKey(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
public static ICollection<T> Clone<T>(this ICollection<T> values)
{
List<T> list = [.. values];
return list;
}
public static int RandomInt(int from, int to)
{
return new Random().Next(from, to);
}
public static string GetSha256Hash(string input)
{
byte[] bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
var builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++) builder.Append(bytes[i].ToString("x2"));
return builder.ToString();
}
public static void SafeAdd<T>(this List<T> list, T item)
{
if (!list.Contains(item)) list.Add(item);
}
public static void SafeAddRange<T>(this List<T> list, List<T> item)
{
foreach (var i in item) list.SafeAdd(i);
}
public static long GetUnixSec()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
public static long ToUnixSec(this DateTime dt)
{
return new DateTimeOffset(dt).ToUnixTimeSeconds();
}
public static long GetUnixMs()
{
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
public static string ToArrayString<T>(this List<T> list)
{
return list.JoinFormat(", ", "");
}
public static string ToJsonString<TK, TV>(this Dictionary<TK, TV> dic) where TK : notnull
{
return JsonConvert.SerializeObject(dic);
}
public static byte[] StringToByteArray(string hex)
{
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
public static int GetHexVal(char hex)
{
int val = (int)hex;
//For uppercase A-F letters:
//return val - (val < 58 ? 48 : 55);
//For lowercase a-f letters:
//return val - (val < 58 ? 48 : 87);
//Or the two combined, but a bit slower:
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
}
#region Kcp Utils
public static string JoinFormat<T>(this IEnumerable<T> list, string separator,
string formatString)
{
formatString = string.IsNullOrWhiteSpace(formatString) ? "{0}" : formatString;
return string.Join(separator,
list.Select(item => string.Format(formatString, item)));
}
public static void WriteConvID(this BinaryWriter bw, long convId)
{
//bw.Write(convId);
bw.Write((int)(convId >> 32));
bw.Write((int)(convId & 0xFFFFFFFF));
}
public static long GetNextAvailableIndex<T>(this SortedList<long, T> sortedList)
{
long key = 1;
long count = sortedList.Count;
long counter = 0;
do
{
if (count == 0) break;
var nextKeyInList = sortedList.Keys.ElementAt((Index)counter++);
if (key != nextKeyInList) break;
key = nextKeyInList + 1;
} while (count != 1 && counter != count && key == sortedList.Keys.ElementAt((Index)counter));
return key;
}
public static long AddNext<T>(this SortedList<long, T> sortedList, T item)
{
var key = sortedList.GetNextAvailableIndex();
sortedList.Add(key, item);
return key;
}
public static int ReadInt32BE(this BinaryReader br)
{
return BinaryPrimitives.ReadInt32BigEndian(br.ReadBytes(sizeof(int)));
}
public static uint ReadUInt32BE(this BinaryReader br)
{
return BinaryPrimitives.ReadUInt32BigEndian(br.ReadBytes(sizeof(uint)));
}
public static ushort ReadUInt16BE(this BinaryReader br)
{
return BinaryPrimitives.ReadUInt16BigEndian(br.ReadBytes(sizeof(ushort)));
}
public static void WriteUInt16BE(this BinaryWriter bw, ushort value)
{
Span<byte> data = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16BigEndian(data, value);
bw.Write(data);
}
public static void WriteInt32BE(this BinaryWriter bw, int value)
{
Span<byte> data = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32BigEndian(data, value);
bw.Write(data);
}
public static void WriteUInt32BE(this BinaryWriter bw, uint value)
{
Span<byte> data = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32BigEndian(data, value);
bw.Write(data);
}
public static void WriteUInt64BE(this BinaryWriter bw, ulong value)
{
Span<byte> data = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64BigEndian(data, value);
bw.Write(data);
}
#endregion
}

View File

@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace KianaBH.Util.Extensions;
public class JsonStringToObjectConverter<T> : JsonConverter<T> where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
return JsonSerializer.Deserialize<T>(ref reader, options);
var jsonString = reader.GetString();
return !string.IsNullOrEmpty(jsonString)
? JsonSerializer.Deserialize<T>(jsonString, options)
: null;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
var json = JsonSerializer.Serialize(value, options);
writer.WriteStringValue(json);
}
}

View File

@@ -0,0 +1,11 @@
namespace KianaBH.Util;
public static class GameConstants
{
public const string GAME_VERSION = "8.2.0";
public const int MAX_STAMINA = 300;
public const int STAMINA_RECOVERY_TIME = 360; // 6 minutes
public const int STAMINA_RESERVE_RECOVERY_TIME = 1080; // 18 minutes
public const int INVENTORY_MAX_EQUIPMENT = 1000;
public const int MAX_LINEUP_COUNT = 9;
}

185
Common/Util/IConsole.cs Normal file
View File

@@ -0,0 +1,185 @@
using Kodnix.Character;
namespace KianaBH.Util;
public class IConsole
{
public static readonly string PrefixContent = "[KianaBH]> ";
public static readonly string Prefix = $"\u001b[38;2;255;192;203m{PrefixContent}\u001b[0m";
private static readonly int HistoryMaxCount = 10;
public static List<char> Input { get; set; } = [];
private static int CursorIndex { get; set; } = 0;
private static readonly List<string> InputHistory = [];
private static int HistoryIndex = -1;
public static event Action<string>? OnConsoleExcuteCommand;
public static void InitConsole()
{
Console.Title = ConfigManager.Config.GameServer.GameServerName;
}
public static int GetWidth(string str)
=> str.ToCharArray().Sum(EastAsianWidth.GetLength);
public static void RedrawInput(List<char> input, bool hasPrefix = true)
=> RedrawInput(new string([.. input]), hasPrefix);
public static void RedrawInput(string input, bool hasPrefix = true)
{
var length = GetWidth(input);
if (hasPrefix)
{
input = Prefix + input;
length += GetWidth(PrefixContent);
}
if (Console.GetCursorPosition().Left > 0)
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write(input + new string(' ', Console.BufferWidth - length));
Console.SetCursorPosition(length, Console.CursorTop);
}
#region Handlers
public static void HandleEnter()
{
var input = new string([.. Input]);
if (string.IsNullOrWhiteSpace(input)) return;
// New line
Console.WriteLine();
Input = [];
CursorIndex = 0;
if (InputHistory.Count >= HistoryMaxCount)
InputHistory.RemoveAt(0);
InputHistory.Add(input);
HistoryIndex = InputHistory.Count;
// Handle command
if (input.StartsWith('/')) input = input[1..].Trim();
OnConsoleExcuteCommand?.Invoke(input);
}
public static void HandleBackspace()
{
if (CursorIndex <= 0) return;
CursorIndex--;
var targetWidth = GetWidth(Input[CursorIndex].ToString());
Input.RemoveAt(CursorIndex);
var (left, _) = Console.GetCursorPosition();
Console.SetCursorPosition(left - targetWidth, Console.CursorTop);
var remain = new string([.. Input.Skip(CursorIndex)]);
Console.Write(remain + new string(' ', targetWidth));
Console.SetCursorPosition(left - targetWidth, Console.CursorTop);
}
public static void HandleUpArrow()
{
if (InputHistory.Count == 0) return;
if (HistoryIndex > 0)
{
HistoryIndex--;
var history = InputHistory[HistoryIndex];
Input = [.. history];
CursorIndex = Input.Count;
RedrawInput(Input);
}
}
public static void HandleDownArrow()
{
if (HistoryIndex >= InputHistory.Count) return;
HistoryIndex++;
if (HistoryIndex >= InputHistory.Count)
{
HistoryIndex = InputHistory.Count;
Input = [];
CursorIndex = 0;
}
else
{
var history = InputHistory[HistoryIndex];
Input = [.. history];
CursorIndex = Input.Count;
}
RedrawInput(Input);
}
public static void HandleLeftArrow()
{
if (CursorIndex <= 0) return;
var (left, _) = Console.GetCursorPosition();
CursorIndex--;
Console.SetCursorPosition(left - GetWidth(Input[CursorIndex].ToString()), Console.CursorTop);
}
public static void HandleRightArrow()
{
if (CursorIndex >= Input.Count) return;
var (left, _) = Console.GetCursorPosition();
CursorIndex++;
Console.SetCursorPosition(left + GetWidth(Input[CursorIndex - 1].ToString()), Console.CursorTop);
}
public static void HandleInput(ConsoleKeyInfo keyInfo)
{
if (char.IsControl(keyInfo.KeyChar)) return;
if (Input.Count >= (Console.BufferWidth - PrefixContent.Length)) return;
HandleInput(keyInfo.KeyChar);
}
public static void HandleInput(char keyChar)
{
Input.Insert(CursorIndex, keyChar);
CursorIndex++;
var (left, _) = Console.GetCursorPosition();
Console.Write(new string([.. Input.Skip(CursorIndex - 1)]));
Console.SetCursorPosition(left + GetWidth(keyChar.ToString()), Console.CursorTop);
}
#endregion
public static string ListenConsole()
{
while (true)
{
ConsoleKeyInfo keyInfo;
try { keyInfo = Console.ReadKey(true); }
catch (InvalidOperationException) { continue; }
switch (keyInfo.Key)
{
case ConsoleKey.Enter:
HandleEnter();
break;
case ConsoleKey.Backspace:
HandleBackspace();
break;
case ConsoleKey.LeftArrow:
HandleLeftArrow();
break;
case ConsoleKey.RightArrow:
HandleRightArrow();
break;
case ConsoleKey.UpArrow:
HandleUpArrow();
break;
case ConsoleKey.DownArrow:
HandleDownArrow();
break;
default:
HandleInput(keyInfo);
break;
}
}
}
}

109
Common/Util/Logger.cs Normal file
View File

@@ -0,0 +1,109 @@
using Spectre.Console;
using System.Diagnostics;
namespace KianaBH.Util;
public class Logger(string moduleName)
{
private static FileInfo? LogFile;
private static readonly object _lock = new();
private readonly string ModuleName = moduleName;
public void Log(string message, LoggerLevel level)
{
lock (_lock)
{
var savedInput = IConsole.Input.ToList(); // Copy
IConsole.RedrawInput("", false);
AnsiConsole.MarkupLine($"[[[bold deepskyblue3_1]{DateTime.Now:HH:mm:ss}[/]]] " +
$"[[[gray]{ModuleName}[/]]] [[[{(ConsoleColor)level}]{level}[/]]] " +
$"{message.Replace("[", "[[").Replace("]", "]]")}");
IConsole.RedrawInput(savedInput);
var logMessage = $"[{DateTime.Now:HH:mm:ss}] [{ModuleName}] [{level}] {message}";
WriteToFile(logMessage);
}
}
public void Info(string message, Exception? e = null)
{
Log(message, LoggerLevel.INFO);
if (e != null)
{
Log(e.Message, LoggerLevel.INFO);
Log(e.StackTrace!, LoggerLevel.INFO);
}
}
public void Warn(string message, Exception? e = null)
{
Log(message, LoggerLevel.WARN);
if (e != null)
{
Log(e.Message, LoggerLevel.WARN);
Log(e.StackTrace!, LoggerLevel.WARN);
}
}
public void Error(string message, Exception? e = null)
{
Log(message, LoggerLevel.ERROR);
if (e != null)
{
Log(e.Message, LoggerLevel.ERROR);
Log(e.StackTrace!, LoggerLevel.ERROR);
}
}
public void Fatal(string message, Exception? e = null)
{
Log(message, LoggerLevel.FATAL);
if (e != null)
{
Log(e.Message, LoggerLevel.FATAL);
Log(e.StackTrace!, LoggerLevel.FATAL);
}
}
public void Debug(string message, Exception? e = null)
{
Log(message, LoggerLevel.DEBUG);
if (e != null)
{
Log(e.Message, LoggerLevel.DEBUG);
Log(e.StackTrace!, LoggerLevel.DEBUG);
}
}
public static void SetLogFile(FileInfo file)
{
LogFile = file;
}
public static void WriteToFile(string message)
{
try
{
if (LogFile == null) throw new Exception("LogFile is not set");
using var sw = LogFile.AppendText();
sw.WriteLine(message);
}
catch
{
}
}
public static Logger GetByClassName()
{
return new Logger(new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "");
}
}
public enum LoggerLevel
{
INFO = ConsoleColor.Cyan,
WARN = ConsoleColor.Yellow,
ERROR = ConsoleColor.Red,
FATAL = ConsoleColor.DarkRed,
DEBUG = ConsoleColor.Blue
}

View File

@@ -0,0 +1,34 @@
using KianaBH.Util;
using Microsoft.AspNetCore.Http;
namespace KianaBH.SdkServer.Utils;
public class RequestLoggingMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context, Logger logger)
{
var request = context.Request;
var method = request.Method;
var path = request.Path + request.QueryString;
await next(context);
var statusCode = context.Response.StatusCode;
if (path.StartsWith("/report") || path.Contains("/log/") || path == "/alive")
return;
if (statusCode == 200)
{
logger.Info($"{method} {path} => {statusCode}");
}
else if (statusCode == 404)
{
logger.Warn($"{method} {path} => {statusCode}");
}
else
{
logger.Error($"{method} {path} => {statusCode}");
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Security.Cryptography;
using System.Text;
namespace KianaBH.Util.Security;
public class Crypto
{
private static readonly Random SecureRandom = new();
// Simple way to create a unique session key
public static string CreateSessionKey(string accountUid)
{
var random = new byte[64];
SecureRandom.NextBytes(random);
var temp = accountUid + "." + DateTime.Now.Ticks + "." + SecureRandom;
try
{
var bytes = SHA512.HashData(Encoding.UTF8.GetBytes(temp));
return Convert.ToBase64String(bytes);
}
catch
{
var bytes = SHA512.HashData(Encoding.UTF8.GetBytes(temp));
return Convert.ToBase64String(bytes);
}
}
}