Files
asesidaa_GC-local-server-re…/GC-local-server-rewrite/controllers/CardServiceController.cs
T
Yuchen Ji c8b8a2816e Finish prototype for web interface
Only unlock unlockable songs at first write
2022-06-21 03:40:42 +08:00

613 lines
18 KiB
C#

using System.Diagnostics;
using System.Net.Mime;
using System.Text;
using System.Xml.Linq;
using ChoETL;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using GCLocalServerRewrite.common;
using GCLocalServerRewrite.models;
using SQLite.Net2;
using Swan;
using Swan.Logging;
namespace GCLocalServerRewrite.controllers;
public class CardServiceController : WebApiController
{
private readonly SQLiteConnection cardSqLiteConnection;
private readonly SQLiteConnection musicSqLiteConnection;
public CardServiceController()
{
cardSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.CardDbName);
musicSqLiteConnection = DatabaseHelper.ConnectDatabase(Configs.SETTINGS.MusicDbName);
}
[Route(HttpVerbs.Post, "/cardn.cgi")]
// ReSharper disable once UnusedMember.Global
public string CardService([FormField] int gid, [FormField("mac_addr")] string mac, [FormField] int type,
[FormField("card_no")] long cardId, [FormField("data")] string xmlData)
{
HttpContext.Response.ContentType = MediaTypeNames.Application.Octet;
HttpContext.Response.ContentEncoding = new UTF8Encoding(false);
HttpContext.Response.KeepAlive = true;
if (!Enum.IsDefined(typeof(CardRequestType), type))
{
throw new ArgumentOutOfRangeException(nameof(type));
}
var requestType = (CardRequestType)type;
switch (requestType)
{
#region ReadRequests
case CardRequestType.ReadCard:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(Card(cardId));
}
case CardRequestType.ReadCardDetail:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(CardDetail(cardId, xmlData));
}
case CardRequestType.ReadCardDetails:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(CardDetails(cardId));
}
case CardRequestType.ReadCardBData:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(CardBData(cardId));
}
case CardRequestType.ReadAvatar:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<Avatar>(cardId, Configs.SETTINGS.AvatarCount, Configs.AVATAR_XPATH));
}
case CardRequestType.ReadItem:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<Item>(cardId, Configs.SETTINGS.ItemCount, Configs.ITEM_XPATH));
}
case CardRequestType.ReadSkin:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<Skin>(cardId, Configs.SETTINGS.SkinCount, Configs.SKIN_XPATH));
}
case CardRequestType.ReadTitle:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<Title>(cardId, Configs.SETTINGS.TitleCount, Configs.TITLE_XPATH));
}
case CardRequestType.ReadMusic:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(MusicUnlock());
}
case CardRequestType.ReadEventReward:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GenerateEmptyXML(Configs.EVENT_REWARD));
}
case CardRequestType.ReadNavigator:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<Navigator>(cardId, Configs.SETTINGS.NavigatorCount, Configs.NAVIGATOR_XPATH));
}
case CardRequestType.ReadMusicExtra:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(MusicExtra());
}
case CardRequestType.ReadMusicAou:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(MusicAouUnlock());
}
case CardRequestType.ReadCoin:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(Coin(cardId));
}
case CardRequestType.ReadUnlockReward:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(UnlockReward(cardId));
}
case CardRequestType.ReadUnlockKeynum:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(UnlockKeynum(cardId));
}
case CardRequestType.ReadSoundEffect:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(
GetStaticCount<SoundEffect>(cardId, Configs.SETTINGS.SeCount, Configs.SE_XPATH));
}
case CardRequestType.ReadGetMessage:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(GenerateEmptyXML(Configs.GET_MESSAGE));
}
case CardRequestType.ReadCond:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(GenerateEmptyXML(Configs.COND));
}
case CardRequestType.ReadTotalTrophy:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(TotalTrophy(cardId));
}
case CardRequestType.SessionStart:
case CardRequestType.SessionGet:
{
$"Getting read request, type is {requestType}".Info();
return ConstructResponse(GetSession(cardId, mac));
}
#endregion
#region WriteRequests
case CardRequestType.WriteCardDetail:
{
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
WriteCardDetail(cardId, xmlData);
return ConstructResponse(xmlData);
}
case CardRequestType.WriteCardBData:
{
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
Write<CardBData>(cardId, xmlData);
WriteCardPlayCount(cardId);
return ConstructResponse(xmlData);
}
case CardRequestType.WriteCard:
{
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
Write<Card>(cardId, xmlData);
return ConstructResponse(xmlData);
}
case CardRequestType.WriteAvatar:
case CardRequestType.WriteItem:
case CardRequestType.WriteTitle:
case CardRequestType.WriteMusicDetail:
case CardRequestType.WriteNavigator:
case CardRequestType.WriteCoin:
case CardRequestType.WriteSkin:
case CardRequestType.WriteUnlockKeynum:
case CardRequestType.WriteSoundEffect:
{
$"Getting write request, type is {requestType}\n Data is {xmlData}".Info();
return ConstructResponse(xmlData);
}
#endregion
default:
#pragma warning disable CA2208
throw new ArgumentOutOfRangeException(nameof(requestType));
#pragma warning restore CA2208
}
}
#region ReadImplementation
private string Card(long cardId)
{
var result = cardSqLiteConnection.Table<Card>().Where(card => card.CardId == cardId);
if (!result.Any())
{
return GenerateEmptyXML(Configs.CARD);
}
var card = result.First();
return GenerateSingleXml(card, Configs.CARD_XPATH);
}
private string CardDetail(long cardId, string xmlData)
{
var reader = new ChoXmlReader<CardDetailReadData>(new StringReader(xmlData));
var data = reader.Read();
var result = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == cardId &&
detail.Pcol1 == data.Pcol1 && detail.Pcol2 == data.Pcol2 &&
detail.Pcol3 == data.Pcol3);
if (!result.Any())
{
return GenerateEmptyXML(Configs.CARD_DETAIL);
}
var cardDetail = result.First();
return GenerateSingleXml(cardDetail, Configs.CARD_DETAIL_SINGLE_XPATH);
}
private string CardDetails(long cardId)
{
var result = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == cardId);
if (!result.Any())
{
return GenerateEmptyXML(Configs.CARD_DETAIL);
}
var cardDetails = result.ToList();
return GenerateRecordsXml(cardDetails, Configs.CARD_DETAIL_RECORD_XPATH);
}
private string CardBData(long cardId)
{
var result = cardSqLiteConnection.Table<CardBData>()
.Where(detail => detail.CardId == cardId);
if (!result.Any())
{
return GenerateEmptyXML(Configs.CARD_BDATA);
}
var cardBData = result.First();
return GenerateSingleXml(cardBData, Configs.CARD_BDATA_XPATH);
}
private static string GetStaticCount<T>(long cardId, int count, string xpath)
where T : Record, IIdModel, ICardIdModel, new()
{
var models = new List<T>();
for (var id = 1; id <= count; id++)
{
var model = new T();
model.SetId(id);
model.SetCardId(cardId);
models.Add(model);
}
return GenerateRecordsXml(models, xpath);
}
private static string GetSession(long cardId, string mac)
{
var session = new Session
{
CardId = cardId,
Mac = mac,
SessionId = "12345678901234567890123456789012",
Expires = 9999,
PlayerId = 1
};
return GenerateSingleXml(session, Configs.SESSION_XPATH);
}
private static string TotalTrophy(long cardId)
{
var trophy = new TotalTrophy
{
CardId = cardId,
TrophyNum = 9
};
return GenerateSingleXml(trophy, Configs.TOTAL_TROPHY_XPATH);
}
private static string Coin(long cardId)
{
var coin = new Coin
{
CardId = cardId,
CurrentCoins = 999999,
TotalCoins = 999999,
MonthlyCoins = 999999
};
return GenerateSingleXml(coin, Configs.COIN_XPATH);
}
private static string UnlockReward(long cardId)
{
var unlockRewards = new List<UnlockReward>
{
new()
{
CardId = cardId,
RewardType = 1,
RewardId = 1,
TargetId = 1,
TargetNum = 1,
KeyNum = 3
}
};
return GenerateRecordsXml(unlockRewards, Configs.UNLOCK_REWARD_XPATH);
}
private static string UnlockKeynum(long cardId)
{
var unlockKeynums = new List<UnlockKeynum>
{
new()
{
CardId = cardId,
RewardId = 1,
KeyNum = 0,
RewardCount = 1
}
};
return GenerateRecordsXml(unlockKeynums, Configs.UNLOCK_KEYNUM_XPATH);
}
private string MusicUnlock()
{
var result = musicSqLiteConnection.Table<Music>().ToList();
return GenerateRecordsXml(result, Configs.MUSIC_XPATH);
}
private string MusicAouUnlock()
{
var result = musicSqLiteConnection.Table<MusicAou>().ToList();
return !result.Any() ? GenerateEmptyXML(Configs.MUSIC_AOU) : GenerateRecordsXml(result, Configs.MUSIC_AOU_XPATH);
}
private string MusicExtra()
{
var result = musicSqLiteConnection.Table<MusicExtra>().ToList();
return GenerateRecordsXml(result, Configs.MUSIC_EXTRA_XPATH);
}
#endregion
#region HelperMethods
private static string ConstructResponse(string xml)
{
return "1\n" +
"10,10\n" +
xml;
}
private static string GenerateEmptyXML(string fieldName)
{
var xml = new XDocument(new XElement("root",
new XElement(fieldName)));
xml.Declaration = new XDeclaration("1.0", "UTF-8", null);
return xml.ToString();
}
private static string GenerateSingleXml<T>(T obj, string xpath) where T: class
{
var sb = new StringBuilder();
using (var writer = new ChoXmlWriter<T>(sb))
{
writer.Configuration.OmitXmlDeclaration = false;
writer.Configuration.UseXmlSerialization = true;
writer.WithXPath(xpath);
writer.Write(obj);
}
return sb.ToString();
}
private static string GenerateRecordsXml<T>(IReadOnlyList<T> list, string xpath) where T : Record
{
var stringBuilder = new StringBuilder();
for (var i = 0; i < list.Count; i++)
{
var obj = list[i];
obj.RecordId = i + 1;
}
using (var writer = new ChoXmlWriter<T>(stringBuilder))
{
writer.Configuration.OmitXmlDeclaration = false;
writer.Configuration.UseXmlSerialization = true;
writer.WithXPath(xpath);
writer.Write(list);
}
return stringBuilder.ToString();
}
#endregion
#region WriteImplementation
private void Write<T>(long cardId, string xmlData) where T : class, ICardIdModel
{
var reader = new ChoXmlReader<T>(new StringReader(xmlData)).WithXPath(Configs.DATA_XPATH);
var writeObject = reader.Read();
if (writeObject == null)
{
throw new HttpRequestException();
}
writeObject.SetCardId(cardId);
var rowsAffected = cardSqLiteConnection.InsertOrReplace(writeObject, typeof(T));
if (rowsAffected == 0)
{
throw new ApplicationException("Update database failed!");
}
$"Updated {typeof(T)}".Info();
}
private void WriteCardPlayCount(long cardId)
{
var record = cardSqLiteConnection.Table<CardPlayCount>().Where(count => count.CardId == cardId);
if (!record.Any())
{
$"Created new play count data for card {cardId}".Info();
var playCount = new CardPlayCount
{
CardId = cardId,
PlayCount = 1,
LastPlayed = DateTime.Now
};
cardSqLiteConnection.InsertOrReplace(playCount);
return;
}
var data = record.First();
var now = DateTime.Now;
var lastPlayedTime = data.LastPlayed;
if (now <= lastPlayedTime)
{
$"Current time {now} is less than or equal to last played time! Clock skew detected!".Warn();
data.PlayCount = 0;
data.LastPlayed = DateTime.Now;
cardSqLiteConnection.InsertOrReplace(data);
return;
}
DateTime start;
DateTime end;
if (now.Hour >= 8)
{
start = DateTime.Today.AddHours(8);
end = start.AddHours(24);
}
else
{
end = DateTime.Today.AddHours(8);
start = end.AddHours(-24);
}
data.PlayCount = lastPlayedTime.IsBetween(start, end) ? data.PlayCount + 1 : 0;
cardSqLiteConnection.InsertOrReplace(data);
$"Updated card play count, current count is {data.PlayCount}".Info();
}
private void WriteCardDetail(long cardId, string xmlData)
{
var result = cardSqLiteConnection.Table<CardDetail>()
.Where(detail => detail.CardId == cardId);
// Unlock all unlockable songs in card details table when write for the first time
if (!result.Any())
{
var unlockableSongIds = Configs.SETTINGS.UnlockableSongIds;
if (unlockableSongIds is null)
{
unlockableSongIds = Configs.DEFAULT_UNLOCKABLE_SONGS;
}
var detailList = unlockableSongIds!.Select(id => new CardDetail
{
CardId = cardId,
Pcol1 = 10,
Pcol2 = id,
Pcol3 = 0,
ScoreUi2 = 1,
ScoreUi6 = 1
})
.ToList();
cardSqLiteConnection.InsertOrIgnoreAll(detailList);
}
Write<CardDetail>(cardId, xmlData);
}
#endregion
private enum CardRequestType
{
// Read data
ReadCard = 259,
ReadCardDetail = 260,
ReadCardDetails = 261,
ReadCardBData = 264,
ReadAvatar = 418,
ReadItem = 420,
ReadSkin = 422,
ReadTitle = 424,
ReadMusic = 428,
ReadEventReward = 441,
ReadNavigator = 443,
ReadMusicExtra = 465,
ReadMusicAou = 467,
ReadCoin = 468,
ReadUnlockReward = 507,
ReadUnlockKeynum = 509,
ReadSoundEffect = 8458,
ReadGetMessage = 8461,
ReadCond = 8465,
ReadTotalTrophy = 8468,
// Sessions
SessionGet = 401,
SessionStart = 402,
// Write data
WriteCard = 771,
WriteCardDetail = 772,
WriteCardBData = 776,
WriteAvatar = 929,
WriteItem = 931,
WriteTitle = 935,
WriteMusicDetail = 941,
WriteNavigator = 954,
WriteCoin = 980,
WriteSkin = 933,
WriteUnlockKeynum = 1020,
WriteSoundEffect = 8969
}
}