Merge branch 'v1-dev' into broker

pull/132/head
Azalea 2025-03-21 16:48:58 -04:00
commit 8efb3d7554
23 changed files with 88 additions and 32 deletions

View File

@ -6,6 +6,8 @@ on:
branches:
- main
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # Runs at midnight UTC every Sunday
jobs:
build-and-push:

View File

@ -9,19 +9,17 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
server-id: github
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Build with Gradle
run: |
mkdir data
bash ./src/main/resources/meta/update.sh
chmod +x gradlew
./gradlew build

9
AquaNet/.env 100644
View File

@ -0,0 +1,9 @@
VITE_AQUA_HOST=https://aquadx.net/aqua
VITE_DATA_HOST=https://aquadx.net
VITE_AQUA_CONNECTION=aquadx.hydev.org
VITE_TURNSTILE_SITE_KEY=0x4AAAAAAASGA2KQEIelo9P9
VITE_DISCORD_INVITE=https://discord.gg/FNgveqFF7s
VITE_TELEGRAM_INVITE=https://t.me/+zBL4RZdyfvUzZGU1
VITE_QQ_INVITE=https://qm.qq.com/q/wvNXbXbHbO

5
AquaNet/.gitignore vendored
View File

@ -32,4 +32,7 @@ dist-ssr
!.yarn/sdks
!.yarn/versions
public/chu3
public/chu3
# local env file
*.local

View File

@ -34,7 +34,7 @@
// Available (unlocked) options for each kind of item
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
let allItems: Record<string, Record<string, { name: string }>> = {}
let iKinds = { namePlate: 1, frame: 2, trophy: 3, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
let userbox: UserBox
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
@ -66,6 +66,10 @@
userItems = Object.entries(iKinds).flatMap(([iKey, iKind]) => {
if (iKey != 'avatarAccessory') {
let ubKey = `${iKey}Id`
if (iKey.slice('trophy'.length, 'trophy'.length + 3) == "Sub") {
ubKey = `trophyIdSub${iKey.slice('trophySub'.length, 'trophySub'.length + 1)}`;
iKey = `trophy`;
}
if (ubKey == 'namePlateId') ubKey = 'nameplateId'
if (ubKey == 'systemVoiceId') ubKey = 'voiceId'
return [{ iKey, ubKey: ubKey as keyof UserBox,

View File

@ -1,15 +1,15 @@
import type { ChusanMatchingOption } from "./generalTypes"
export const AQUA_HOST = 'https://aquadx.net/aqua'
export const DATA_HOST = 'https://aquadx.net'
export const AQUA_HOST = import.meta.env.VITE_AQUA_HOST
export const DATA_HOST = import.meta.env.VITE_DATA_HOST
// This will be displayed for users to connect from the client
export const AQUA_CONNECTION = 'aquadx.hydev.org'
export const AQUA_CONNECTION = import.meta.env.VITE_AQUA_CONNECTION
export const TURNSTILE_SITE_KEY = '0x4AAAAAAASGA2KQEIelo9P9'
export const DISCORD_INVITE = 'https://discord.gg/FNgveqFF7s'
export const TELEGRAM_INVITE = 'https://t.me/+zBL4RZdyfvUzZGU1'
export const QQ_INVITE = 'https://qm.qq.com/q/wvNXbXbHbO'
export const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY
export const DISCORD_INVITE = import.meta.env.VITE_DISCORD_INVITE
export const TELEGRAM_INVITE = import.meta.env.VITE_TELEGRAM_INVITE
export const QQ_INVITE = import.meta.env.VITE_QQ_INVITE
// UI
export const FADE_OUT = { duration: 200 }

View File

@ -142,6 +142,8 @@ export interface UserBox {
frameId: number,
characterId: number,
trophyId: number,
trophyIdSub1: number,
trophyIdSub2: number,
mapIconId: number,
voiceId: number,
avatarWear: number,

View File

@ -191,6 +191,8 @@ export const EN_REF_USERBOX = {
'userbox.nameplateId': 'Nameplate',
'userbox.frameId': 'Frame',
'userbox.trophyId': 'Trophy (Title)',
'userbox.trophyIdSub1': 'Trophy Sub #1 (Title)',
'userbox.trophyIdSub2': 'Trophy Sub #2 (Title)',
'userbox.mapIconId': 'Map Icon',
'userbox.voiceId': 'System Voice',
'userbox.avatarWear': 'Avatar Wear',

View File

@ -318,7 +318,13 @@
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} game={game != "auto" ? game : "mai2"}/>
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
<!-- Chuni -->
{#if d.user.ratingComposition.new}
<RatingComposition title="New 20" comp={d.user.ratingComposition.new} {allMusics} game="chu3"/>
{:else}
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
{/if}
<div class="recent">
<h2>{t('UserHome.RecentScores')}</h2>

View File

@ -51,7 +51,7 @@
return submitting = false
}
if (turnstile === "") {
if (TURNSTILE_SITE_KEY && turnstile === "") {
// Sleep for 100ms to allow Turnstile to finish
error = t("welcome.waiting-turnstile")
return setTimeout(submit, 100)
@ -137,11 +137,13 @@
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
{/if}
</button>
{#if TURNSTILE_SITE_KEY}
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
on:turnstile-error={_ => console.log(error = t("welcome.turnstile-error"))}
on:turnstile-expired={_ => window.location.reload()}
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
{/if}
</div>
{:else if state === "verify"}
<div class="login-form" transition:slide>

View File

@ -33,7 +33,7 @@ Below is a list of games supported by this server.
| Game | Ver | Codename | Thanks to |
|------------------------|------|---------------------|--------------------------------------------|
| SDHD: CHUNITHM | 2.30 | VERSE¹ | [@rinsama](https://github.com/mxihan) |
| SDHD: CHUNITHM | 2.30 | VERSE | [@rinsama](https://github.com/mxihan) |
| SDEZ: MaiMai DX | 1.50 | PRiSM | [@肥宅虾哥](https://github.com/FeiZhaixiage) |
| SDGA: MaiMai DX (Intl) | 1.50 | PRiSM | [@Clansty](https://github.com/clansty) |
| SDED: Card Maker | 1.39 | | [@Becods](https://github.com/Becods) |
@ -41,8 +41,6 @@ Below is a list of games supported by this server.
| SBZV: Project DIVA | 7.10 | Future Tone | |
| SDFE: Wacca (*ALPHA) | 3.07 | Reverse | |
* ¹: For VERSE, normal gameplay works, but events and new features might be missing. Luminous+ and older are fully supported.
Check out these docs for more information.
* [Game specific notes](docs/game_specific_notes.md)
* [Frequently asked questions](docs/frequently_asked_questions.md)

View File

@ -30,7 +30,7 @@ allnet.server.redirect=https://aquadx.net
## Http Server Port
## Only change this if you have a reverse proxy running.
## The game rely on 80 port for boot up command
server.port=8080
server.port=80
## Static file server
## This is used to server static files in /web/ directory, which is Aquaviewer

View File

@ -45,6 +45,13 @@ netsh advfirewall firewall add rule name="Chunithm National Matching Outbound" d
## Troubleshooting
**Q: Me and my friend are queuing but we can't join the same room**
Make sure you both have the same ROM and options (e.g. it would not work if you have luminuous and they have verse, or if you have A121 while they don't).
> [!NOTE]
> If you just updated your options, your matching will be disabled because of data version mismatch. You need to play for a session, save, and then restart your game for the server-side data version to update.
**Q: Matching server BAD on network check**
Make sure you have selected "Yukiotoko" as your matching server.

View File

@ -29,6 +29,8 @@ class Chusan(
"nameplateId" to { u, v -> u.nameplateId = v.int },
"frameId" to { u, v -> u.frameId = v.int },
"trophyId" to { u, v -> u.trophyId = v.int },
"trophyIdSub1" to { u, v -> u.trophyIdSub1 = v.int },
"trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int },
"mapIconId" to { u, v -> u.mapIconId = v.int },
"voiceId" to { u, v -> u.voiceId = v.int },
"avatarWear" to { u, v -> u.avatarWear = v.int },
@ -52,6 +54,7 @@ class Chusan(
"best30" to (extra["rating_base_list"] ?: ""),
"hot10" to (extra["rating_hot_list"] ?: ""),
"next10" to (extra["rating_next_list"] ?: ""),
"new" to (extra["rating_new_list"] ?: ""),
)
genericUserSummary(card, ratingComposition)

View File

@ -96,7 +96,7 @@ class AllNet(
@PostMapping("/sys/servlet/PowerOn", produces = ["text/plain"])
fun powerOn(dataStream: InputStream, req: HttpServletRequest): String {
val localAddr = req.localAddr
val here = req.getHeader("AllNet-Forwarded-From") ?: props.host.ifBlank { req.localAddr }
val localPort = req.localPort.toString()
// game_id SDEZ, ver 1.35, serial A0000001234, ip, firm_ver 50000, boot_ver 0000,
@ -138,8 +138,8 @@ class AllNet(
val formatVer = reqMap["format_ver"] ?: ""
val resp = props.map.mut + mapOf(
"uri" to switchUri(localAddr, localPort, gameId, ver, session),
"host" to props.host.ifBlank { localAddr },
"uri" to switchUri(here, localPort, gameId, ver, session),
"host" to props.host.ifBlank { here },
)
// Different responses for different versions
@ -171,9 +171,8 @@ class AllNet(
return resp.toUrl() + "\n"
}
private fun switchUri(localAddr: Str, localPort: Str, gameId: Str, ver: Str, session: Str?): Str {
val addr = props.host.ifBlank { localAddr } +
if (props.hidePort) "" else ":${props.port ?: localPort}"
private fun switchUri(hereAddr: Str, localPort: Str, gameId: Str, ver: Str, session: Str?): Str {
val addr = hereAddr + (if (props.hidePort) "" else ":${props.port ?: localPort}")
// If keychip authentication is enabled, the game URLs will be set to /gs/{token}/{game}/...
val base = if (session != null) "gs/$session" else "g"

View File

@ -1,9 +1,11 @@
package icu.samnyan.aqua.sega.cardmaker.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.allnet.KeychipSession;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.cardmaker.model.response.data.GameConnect;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import icu.samnyan.aqua.sega.allnet.TokenChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -32,7 +34,7 @@ public class GetGameConnectHandler implements BaseHandler {
@Autowired
public GetGameConnectHandler(BasicMapper mapper, @Value("${allnet.server.host:}") String ALLNET_HOST,
@Value("${allnet.server.port:}") String ALLNET_PORT, @Value("${server.port:}") String SERVER_PORT) {
@Value("${allnet.server.port:}") String ALLNET_PORT, @Value("${server.port:}") String SERVER_PORT) {
this.mapper = mapper;
this.ALLNET_HOST = ALLNET_HOST;
this.ALLNET_PORT = ALLNET_PORT;
@ -43,7 +45,8 @@ public class GetGameConnectHandler implements BaseHandler {
public String handle(Map<String, ?> request) throws JsonProcessingException {
int type = ((Number) request.get("type")).intValue(); // Allnet enabled or not
long version = ((Number) request.get("version")).longValue(); // Rom version
KeychipSession session = TokenChecker.Companion.getCurrentSession();
// Unless ip and port is explicitly overridden, use the guessed ip and port as same as AllNet Controller does.
String localAddr;
try {
@ -56,10 +59,11 @@ public class GetGameConnectHandler implements BaseHandler {
String addr = ALLNET_HOST.equals("") ? localAddr : ALLNET_HOST;
String port = ALLNET_PORT.equals("") ? SERVER_PORT : ALLNET_PORT;
String base = session == null ? "/g" : "/gs/" + session.getToken();
List<GameConnect> gameConnectList = new ArrayList<>();
GameConnect chuni = new GameConnect(0, 1, "http://" + addr + ":" + port + "/g/chu3/" + version + "/");
GameConnect mai = new GameConnect(1, 1, "http://" + addr + ":" + port + "/g/mai2/");
GameConnect ongeki = new GameConnect(2, 1, "http://" + addr + ":" + port + "/g/ongeki/");
GameConnect chuni = new GameConnect(0, 1, "http://" + addr + ":" + port + base + "/chu3/" + version + "/");
GameConnect mai = new GameConnect(1, 1, "http://" + addr + ":" + port + base + "/mai2/");
GameConnect ongeki = new GameConnect(2, 1, "http://" + addr + ":" + port + base + "/ongeki/");
gameConnectList.add(chuni);
gameConnectList.add(mai);
gameConnectList.add(ongeki);

View File

@ -0,0 +1,17 @@
ALTER TABLE chusan_user_data ADD COLUMN trophy_id_sub1 INT NOT NULL DEFAULT 0;
ALTER TABLE chusan_user_data ADD COLUMN trophy_id_sub2 INT NOT NULL DEFAULT 0;
CREATE TABLE chusan_user_challenge
(
id BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY,
user_id BIGINT NOT NULL,
unlock_challenge_id INT NOT NULL,
status INT NOT NULL,
clear_course_id INT NOT NULL,
condition_type INT NOT NULL,
score INT NOT NULL,
life INT NOT NULL,
clear_date VARCHAR(20) NULL,
CONSTRAINT fku_chusan_user_challenge FOREIGN KEY (user_id) REFERENCES chusan_user_data (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT unique_user_challenge UNIQUE (user_id, unlock_challenge_id)
);