Merge branch 'v1-dev' into v1-dev

pull/55/head
凌莞~(=^▽^=) 2024-10-04 19:23:16 +08:00 committed by GitHub
commit ac01469eac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 4801 additions and 58 deletions

View File

@ -300,6 +300,7 @@ DEBUG</DefineConstants>
<Compile Include="Fix\FixCharaCrash.cs" />
<Compile Include="Fix\FixCheckAuth.cs" />
<Compile Include="Fix\FixConnSlide.cs" />
<Compile Include="Fix\FontFix.cs" />
<Compile Include="Fix\ForceAsServer.cs" />
<Compile Include="Fix\ForceFreePlay.cs" />
<Compile Include="Fix\ForcePaidPlay.cs" />
@ -337,6 +338,7 @@ DEBUG</DefineConstants>
<Compile Include="Utils\PractiseModeUI.cs" />
<Compile Include="Utils\SelectionDetail.cs" />
<Compile Include="Utils\ShowNetErrorDetail.cs" />
<Compile Include="UX\CustomFont.cs" />
<Compile Include="UX\CustomPlaceName.cs" />
<Compile Include="UX\CustomVersionString.cs" />
<Compile Include="UX\DemoMaster.cs" />

View File

@ -50,6 +50,9 @@ CustomPlaceName=""
# In the song selection screen, press the Service button or the "7" key (the round button in the middle of the arrow keys in the default ADX firmware) to toggle the display of self-made charts.
# A directory is considered to contain self-made charts if it does not have DataConfig.xml or OfficialChartsMark.txt in the Axxx directory.
HideSelfMadeCharts=true
# Place font.ttf in the LocalAssets directory to replace the game's global font
# Cannot be used together with FontFix
CustomFont=false
[Fix]
# Allow login with higher data version
@ -64,6 +67,9 @@ ForcePaidPlay=false
ExtendNotesPool=128
# Force the frame rate limit to 60 FPS and disable vSync. Do not use if your game has no issues
FrameRateLock=false
# Use Microsoft YaHei Bold to display characters not in the font library
# Cannot be used together with CustomFont
FontFix=true
[Utils]
# Log user ID on login
@ -74,16 +80,13 @@ JudgeAdjustA=0.0
JudgeAdjustB=0.0
# Touch screen delay, unit is milliseconds, one second = 1000 milliseconds. Must be an integer
TouchDelay=0
# Window the game
Windowed=false
# Width and height for windowed mode, rendering resolution for fullscreen mode
# If set to 0, windowed mode will remember the user-set size, fullscreen mode will use the current display resolution
Width=0
Height=0
# Show detail of selected song in music selection screen
SelectionDetail=true
# Display framerate
FrameRateDisplay=false
# Practice mode, activated by pressing Test in the game
# Must be used together with TestProof
PractiseMode=true
# ===================================
# Save some potentially unnecessary time
@ -105,6 +108,16 @@ SkipTrackStart=true
# Show reason when net icon is gray
ShowNetErrorDetail=true
[WindowState]
# If not enabled, no operations will be performed on the game window
Enable=false
# Window the game
Windowed=false
# Width and height for windowed mode, rendering resolution for fullscreen mode
# If set to 0, windowed mode will remember the user-set size, fullscreen mode will use the current display resolution
Width=0
Height=0
[TouchSensitivity]
# Enable custom sensitivity
# When enabled, the settings in Test mode will not take effect
@ -150,3 +163,27 @@ E5=20
E6=20
E7=20
E8=20
[CustomKeyMap]
Enable = false
# These settings will work regardless of whether you have enabled segatools' io4 emulation
Test = "ScrollLock"
Service = "Pause"
Button1_1P = "W"
Button2_1P = "E"
Button3_1P = "D"
Button4_1P = "C"
Button5_1P = "X"
Button6_1P = "Z"
Button7_1P = "A"
Button8_1P = "Q"
Select_1P = "Alpha3"
Button1_2P = "Keypad8"
Button2_2P = "Keypad9"
Button3_2P = "Keypad6"
Button4_2P = "Keypad3"
Button5_2P = "Keypad2"
Button6_2P = "Keypad1"
Button7_2P = "Keypad4"
Button8_2P = "Keypad7"
Select_2P = "KeypadMultiply"

View File

@ -59,6 +59,9 @@ CustomPlaceName=""
# 选歌界面按下 Service 键或者键盘上的 “7” 键ADX 默认固件下箭头键中间的圆形按键)切换自制谱的显示和隐藏
# 是否是自制谱的判断方式是 Axxx 目录里没有 DataConfig.xml 或 OfficialChartsMark.txt 就认为这个目录里是自制谱
HideSelfMadeCharts=true
# 在 LocalAssets 目录下放置 font.ttf 可以替换游戏的全局字体
# 不可以和 FontFix 一起使用
CustomFont=false
# ===================================
# 修复一些潜在的问题
@ -80,6 +83,9 @@ ForcePaidPlay=false
ExtendNotesPool=128
# 强制设置帧率上限为 60 帧并关闭垂直同步。如果你的游戏没有问题,请不要使用
FrameRateLock=false
# 在显示字库里没有的字时使用微软雅黑 Bold 显示
# 不可以和 CustomFont 一起使用
FontFix=true
[Utils]
# 登录时将 UserID 输出到日志
@ -90,18 +96,15 @@ JudgeAdjustA=0.0
JudgeAdjustB=0.0
# 触摸屏延迟,单位为毫秒,一秒 = 1000 毫秒。必须是整数
TouchDelay=0
# 窗口化游戏
Windowed=false
# 宽度和高度窗口化时为游戏窗口大小,全屏时为渲染分辨率
# 如果设为 0窗口化将记住用户设定的大小全屏时将使用当前显示器分辨率
Width=0
Height=0
# 选歌界面显示选择的歌曲的详情
SelectionDetail=true
# 出现灰网时显示原因
ShowNetErrorDetail=true
# 显示帧率
FrameRateDisplay=false
# 练习模式,在游戏中按 Test 打开
# 必须和 TestProof 一起用
PractiseMode=true
# ===================================
# 节省一些不知道有用没用的时间
@ -122,6 +125,16 @@ SkipGameOverScreen=true
# 跳过乐曲开始界面
SkipTrackStart=true
[WindowState]
# 不启用的话,不会对游戏窗口做任何操作
Enable=false
# 窗口化游戏
Windowed=false
# 宽度和高度窗口化时为游戏窗口大小,全屏时为渲染分辨率
# 如果设为 0窗口化将记住用户设定的大小全屏时将使用当前显示器分辨率
Width=0
Height=0
[TouchSensitivity]
# 是否启用自定义灵敏度
# 这里启用之后 Test 里的就不再起作用了
@ -167,3 +180,27 @@ E5=20
E6=20
E7=20
E8=20
[CustomKeyMap]
Enable = false
# 这里的设置无论你是否启用了 segatools 的 io4 模拟都会工作
Test = "ScrollLock"
Service = "Pause"
Button1_1P = "W"
Button2_1P = "E"
Button3_1P = "D"
Button4_1P = "C"
Button5_1P = "X"
Button6_1P = "Z"
Button7_1P = "A"
Button8_1P = "Q"
Select_1P = "Alpha3"
Button1_2P = "Keypad8"
Button2_2P = "Keypad9"
Button3_2P = "Keypad6"
Button4_2P = "Keypad3"
Button5_2P = "Keypad2"
Button6_2P = "Keypad1"
Button7_2P = "Keypad4"
Button8_2P = "Keypad7"
Select_2P = "KeypadMultiply"

View File

@ -38,6 +38,7 @@ namespace AquaMai
public bool LoadLocalBga { get; set; }
public bool TestProof { get; set; }
public bool HideSelfMadeCharts { get; set; }
public bool CustomFont { get; set; }
public string CustomVersionString { get; set; } = "";
public string CustomPlaceName { get; set; } = "";
public string ExecOnIdle { get; set; } = "";
@ -53,6 +54,7 @@ namespace AquaMai
public bool ForcePaidPlay { get; set; }
public int ExtendNotesPool { get; set; }
public bool FrameRateLock { get; set; }
public bool FontFix { get; set; }
}
public class UtilsConfig

View File

@ -47,14 +47,6 @@ public class BasicFix
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NetHttpClient), "CheckServerHash")]
private static bool CheckServerHash(ref bool __result)
{
__result = true;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GameManager), "CalcSpecialNum")]
private static bool CalcSpecialNum(ref int __result)
@ -81,4 +73,12 @@ public class BasicFix
break;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(NetHttpClient), "CheckServerHash")]
private static bool CheckServerHash(ref bool __result)
{
__result = true;
return false;
}
}

View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
using HarmonyLib;
using MelonLoader;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
namespace AquaMai.Fix;
public class FontFix
{
private static TMP_FontAsset fontAsset;
private static List<TMP_FontAsset> fixedFonts = [];
public static void DoCustomPatch(HarmonyLib.Harmony h)
{
var font = new Font(@"C:\Windows\Fonts\msyhbd.ttc");
fontAsset = TMP_FontAsset.CreateFontAsset(font, 90, 9, GlyphRenderMode.SDFAA, 8192, 8192);
}
[HarmonyPatch(typeof(TextMeshProUGUI), "Awake")]
[HarmonyPostfix]
public static void PostFix(TextMeshProUGUI __instance)
{
if (fixedFonts.Contains(__instance.font)) return;
# if DEBUG
MelonLogger.Msg($"[FontFix] Fixing font: {__instance.font.name}");
# endif
__instance.font.fallbackFontAssetTable.Add(fontAsset);
fixedFonts.Add(__instance.font);
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using HarmonyLib;
using MelonLoader;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
namespace AquaMai.UX;
public class CustomFont
{
private static Font font;
private static TMP_FontAsset fontAsset;
public static void DoCustomPatch(HarmonyLib.Harmony h)
{
var fontPath = Path.Combine(Environment.CurrentDirectory, "LocalAssets", "font.ttf");
if (!File.Exists(fontPath)) return;
font = new Font(fontPath);
fontAsset = TMP_FontAsset.CreateFontAsset(font, 90, 9, GlyphRenderMode.SDFAA, 8192, 8192);
}
[HarmonyPatch(typeof(TextMeshProUGUI), "Awake")]
[HarmonyPostfix]
public static void PostFix(TextMeshProUGUI __instance)
{
if (font is null) return;
# if DEBUG
MelonLogger.Msg($"{__instance.font.name} {__instance.text}");
# endif
var materialOrigin = __instance.fontMaterial;
var materialSharedOrigin = __instance.fontSharedMaterial;
__instance.font = fontAsset;
# if DEBUG
MelonLogger.Msg($"shaderKeywords {materialOrigin.shaderKeywords.Join()} {__instance.fontMaterial.shaderKeywords.Join()}");
# endif
// __instance.fontSharedMaterial = materialSharedOrigin;
// 这样之后该有描边的地方整个字后面都是阴影,它不知道哪里是边
// materialOrigin.mainTexture = __instance.fontMaterial.mainTexture;
// materialOrigin.mainTextureOffset = __instance.fontMaterial.mainTextureOffset;
// materialOrigin.mainTextureScale = __instance.fontMaterial.mainTextureScale;
// __instance.fontMaterial.CopyPropertiesFromMaterial(materialOrigin);
// 这样了之后有描边了,但是描边很细
// __instance.fontMaterial.shader = materialOrigin.shader;
foreach (var keyword in materialOrigin.shaderKeywords)
{
__instance.fontMaterial.EnableKeyword(keyword);
}
// __instance.fontMaterial.globalIlluminationFlags = materialOrigin.globalIlluminationFlags;
// 原来是 underlay但是复制这三个属性之后就又变成整个字后面都是阴影了
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetY, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetY));
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX));
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayDilate, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayDilate));
// if(materialOrigin.shaderKeywords.Contains(ShaderUtilities.Keyword_Underlay))
// {
// __instance.fontMaterial.EnableKeyword(ShaderUtilities.Keyword_Glow);
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_GlowOuter, .5f);
// // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX));
// }
}
}

View File

@ -20,6 +20,7 @@ namespace AquaMai.UX
{
left.transform.position = Vector3.zero;
right.localScale = Vector3.zero;
GameObject.Find("Mask").transform.position = new Vector3(540f, 0f, 0f);
}
[HarmonyPrefix]

View File

@ -81,6 +81,9 @@ public class PractiseModeUI : MonoBehaviour
GUI.Label(GetButtonRect(1, 2), $"{Locale.Speed} {PractiseMode.speed * 100:000}%");
GUI.Button(GetButtonRect(2, 2), Locale.SpeedUp);
GUI.Button(GetButtonRect(1, 3), Locale.SpeedReset);
GUI.Label(GetButtonRect(0, 3), TimeSpan.FromMilliseconds(DebugFeature.CurrentPlayMsec).ToString(@"mm\:ss\.fff"));
GUI.Label(GetButtonRect(2, 3), TimeSpan.FromMilliseconds(NotesManager.Instance().getPlayFinalMsec()).ToString(@"mm\:ss\.fff"));
}
public void Update()

View File

@ -16,6 +16,7 @@
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"@tsconfig/svelte": "^5.0.4",
"@types/d3": "^7",
"@types/wicg-file-system-access": "^2023.10.5",
"@unocss/svelte-scoped": "^0.62.4",
"chartjs-adapter-moment": "^1.0.1",
"eslint": "^8.57.0",
@ -39,7 +40,8 @@
"lxgw-wenkai-lite-webfont": "^1.7.0",
"modern-normalize": "^2.0.0",
"moment": "^2.30.1",
"show-open-file-picker": "^0.2.2",
"svelte-chartjs": "^3.1.5"
},
"packageManager": "yarn@4.1.1"
"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
}

File diff suppressed because it is too large Load Diff

View File

@ -305,6 +305,8 @@ export const GAME = {
post(`/api/v2/game/${game}/export`),
import: (game: GameName, data: any): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/import`, {}, { body: JSON.stringify(data) }),
importMusicDetail: (game: GameName, data: any): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/import-music-detail`, {}, {body: JSON.stringify(data), headers: {'Content-Type': 'application/json'}}),
setRival: (game: GameName, rivalUserName: string, isAdd: boolean) =>
post(`/api/v2/game/${game}/set-rival`, { rivalUserName, isAdd }),
}

View File

@ -5,6 +5,7 @@
import StatusOverlays from "../../components/StatusOverlays.svelte";
import { CARD, GAME, USER } from "../../libs/sdk";
import Icon from "@iconify/svelte";
import { showOpenFilePicker } from 'show-open-file-picker'
let load = false;
let error = "";
@ -17,8 +18,8 @@
let confirmAction: (override: boolean) => void;
const startImport = async () => {
const [fileHandle] = await window.showOpenFilePicker({
id: 'aquadx_import',
const [fileHandle] = await (window.showOpenFilePicker || showOpenFilePicker)({
id: 'aquadx_import' as any,
startIn: 'downloads',
types: [
{
@ -36,9 +37,17 @@
try {
const file = await fileHandle.getFile();
const data = JSON.parse(await file.text()) as any;
const game = getGameByCode(data.gameId);
const me = await USER.me();
const maybeUserMusicList = data?.userMusicList || data;
if (Array.isArray(maybeUserMusicList) && maybeUserMusicList.every(it => Array.isArray(it?.userMusicDetailList))) {
// Is music list array
await GAME.importMusicDetail("mai2", maybeUserMusicList.flatMap(it => it.userMusicDetailList));
location.href = `/u/${me.username}/mai2`;
return;
}
const game = getGameByCode(data.gameId);
const userGames = await CARD.userGames(me.username);
const existed = userGames[game];
@ -78,42 +87,42 @@
</script>
<ActionCard color="209, 124, 102" icon="bxs:file-import" on:click={startImport}>
<h3>{t('home.import')}</h3>
<span>{t('home.import-description')}</span>
<h3>{t('home.import')}</h3>
<span>{t('home.import-description')}</span>
</ActionCard>
<StatusOverlays {error} loading={load}/>
{#if conflict}
<div class="overlay" transition:fade>
<div>
<h2>{t('home.import.data-conflict')}</h2>
<p></p>
<div class="conflict-cards">
<div class="old card">
<span class="type">{t('home.linkcard.account-card')}</span>
<span>{t('home.linkcard.name')}: {conflict.oldName}</span>
<span>{t('home.linkcard.rating')}: {conflict.oldRating}</span>
<div class="trash">
<Icon icon="ph:trash-duotone"/>
</div>
<div class="overlay" transition:fade>
<div>
<h2>{t('home.import.data-conflict')}</h2>
<p></p>
<div class="conflict-cards">
<div class="old card">
<span class="type">{t('home.linkcard.account-card')}</span>
<span>{t('home.linkcard.name')}: {conflict.oldName}</span>
<span>{t('home.linkcard.rating')}: {conflict.oldRating}</span>
<div class="trash">
<Icon icon="ph:trash-duotone"/>
</div>
</div>
<div class="icon">
<Icon icon="icon-park-outline:down"/>
</div>
<div class="new card">
<span class="type">{t('home.import.new-data')}</span>
<span>{t('home.linkcard.name')}: {conflict.newName}</span>
<span>{t('home.linkcard.rating')}: {conflict.newRating}</span>
</div>
</div>
<p></p>
<div class="buttons">
<button on:click={() => confirmAction(false)}>{t('action.cancel')}</button>
<button class="error" on:click={() => confirmAction(true)}>{t('action.confirm')}</button>
</div>
</div>
<div class="icon">
<Icon icon="icon-park-outline:down"/>
</div>
<div class="new card">
<span class="type">{t('home.import.new-data')}</span>
<span>{t('home.linkcard.name')}: {conflict.newName}</span>
<span>{t('home.linkcard.rating')}: {conflict.newRating}</span>
</div>
</div>
<p></p>
<div class="buttons">
<button on:click={() => confirmAction(false)}>{t('action.cancel')}</button>
<button class="error" on:click={() => confirmAction(true)}>{t('action.confirm')}</button>
</div>
</div>
</div>
{/if}
<style lang="sass">

View File

@ -130,11 +130,12 @@ class AquaUserServices(
suspend fun <T> byName(username: Str, callback: suspend (AquaNetUser) -> T) =
async { userRepo.findByUsernameIgnoreCase(username) }?.let { callback(it) } ?: (404 - "User not found")
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) =
suspend fun cardByName(username: Str) =
if (username.startsWith("user")) username.substring(4).toLongOrNull()
?.let { cardRepo.findById(it).getOrNull() }
?.let { callback(it) } ?: (404 - "Card not found")
else byName(username) { callback(it.ghostCard) }
?.let { cardRepo.findById(it).getOrNull() } ?: (404 - "Card not found")
else byName(username) { it.ghostCard }
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username))
fun validKeychip(keychipId: Str): Bool {
if (!allNetProps.checkKeychip) return true

View File

@ -0,0 +1,45 @@
package icu.samnyan.aqua.net.games.mai2
import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@API("api/v2/game/mai2")
class Mai2MusicDetailImport(
val us: AquaUserServices,
val repos: Mai2Repos,
) {
@PostMapping("import-music-detail")
suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { card ->
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found")
data.forEach { newMusic ->
val musicRec = repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)
if (musicRec.isPresent) {
val music = musicRec.get()
newMusic.apply {
id = music.id
this.user = user
achievement = achievement.coerceAtLeast(music.achievement)
scoreRank = scoreRank.coerceAtLeast(music.scoreRank)
comboStatus = comboStatus.coerceAtLeast(music.comboStatus)
syncStatus = syncStatus.coerceAtLeast(music.syncStatus)
deluxscoreMax = deluxscoreMax.coerceAtLeast(music.deluxscoreMax)
playCount = playCount.coerceAtLeast(music.playCount)
}
} else {
newMusic.apply {
this.user = user
}
}
}
repos.userMusicDetail.saveAll(data)
SUCCESS
}
}
}