Added initial Chinese game version support

- added `zh-cn` locale
- added `server` field to `config.yaml` file that indicates
  currently using game server - `global` or `cn`
  its default value is based on the system language
- `constants.paths.gameDataDir` returns path based on `server` config property
- `constants.placeholders.uppercase.full` was changed to an object:
  + global: <global game name>
  + cn: <cn game name>
- in `constants.uri.*` changed second fields:
  + api
  + telemetry
  they're following the same format as `full` placeholder
- `constants.versionsUri()` and
  `constants.backgroundUri()` are functions now
  their output depends on server you're passing as a parameter
- added `Game.server` field (global / cn)
- `PatchInfo` type now contains `server` field
- `Patch.getPatchInfo()` also depends on `Game.server` value
- reworked `Background.get()` method to support cn game api
This commit is contained in:
Observer KRypt0n_ 2022-02-20 13:29:06 +02:00
parent 800dee183a
commit 75f2ad66ed
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
24 changed files with 503 additions and 43 deletions

View file

@ -48,6 +48,16 @@ flatpak install launcher.moe com.gitlab.KRypt0n_.an-anime-game-launcher
Some additional configuration is possible when using the flatpak. For info about this, see [this page](https://gitlab.com/lane__/an-anime-game-launcher-flatpak/-/blob/master/README.md)
## Chinese version
To use Chinese version of the game make sure that you have
```yaml
server: cn
```
field in your `config.yaml` file (settings -> launcher folder). If your system uses the Chinese language - then it should be selected automatically. If not, then close the launcher, replace `server: global` with `server: cn`, and run it again. After that launcher will download the Chinese version of the game and the patch
# Status
| Game version | Launcher version | Patch version |
@ -159,8 +169,8 @@ This is our current roadmap goals. You can find older ones [here](repository/pag
* <s>ToS Violation Window</s> *(2.1.0)*
* <s>Add `latest.log` file generation</s> *(2.1.2)*
* <s>Add an option to show terminal with the wine's log of the game</s> *(7375c743, released in 2.1.3)*
* <s>Statistics window</s>
* Add Chinese game's version support (due to changes in the Krock's patch)
* <s>Statistics window</s> *(e0a09323)*
* <s>Add Chinese game's version support (due to changes in the Krock's patch)</s>
* Implement manual config flushing functionality from the Empathize's API
* Add analytics window
* Dark progress bar design

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Spiel sprachpaket auswahl

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Idioma del paquete de voz

View file

@ -95,6 +95,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Язык озвучки в игре

View file

@ -92,6 +92,7 @@ settings:
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language

344
public/locales/zh-cn.yaml Normal file
View file

@ -0,0 +1,344 @@
# Splash window
splash:
title: Loading launcher
phrases:
- Doing some important stuff...
- Bullying Paimon...
- Pulling for Yae...
- Farming materials...
- Passing Abyss...
- Collecting achievements...
- Building phys Qiqi...
- Making sacrifices for 5*...
- Finding friends for co-op...
# Launcher window
launcher:
# Progress bar
progress:
pause: Pause
resume: Resume
# Game installation
game:
downloading: Downloading game...
unpacking: Unpacking game...
applying_changes: Applying changes...
deleting_outdated: Deleting outdated files...
# Voice packages installation
voice:
deleting: Deleting voice packages...
downloading: Downloading {voice} voice package...
unpacking: Unpacking {voice} voice package...
# Launcher states
states:
# When the game should be installed or updated
installation:
install_wine: Install Wine
install_dxvk: Install DXVK
install: Install
update: Update
apply_changes:
title: Apply changes
hint: Apply hdiff changes to the game files
remove_outdated:
title: Remove outdated
hint: Remove outdated game files
# When the game should be patched
patching:
# Patch unavailable
unavailable:
title: Patch unavailable
hint: This game version has no anti-cheat patch.
Please, wait a few days before there will be a test or stable version
# Patch is in testing
test:
title: Apply test patch
hint: This game version has an experimental anti-cheat patch.
You can wait a few days until it is stable or apply it at your own risk
# Patch is stable
stable: Apply patch
# When the game is ready for playing
ready:
launch: Launch
predownload: Pre-download update
# Settings window
settings:
# General
general:
title: General
items:
# Language selection
lang:
# Launcher language
launcher:
title: Launcher
items:
en-us: English (US)
ru-ru: Русский
es-es: Español
de-de: Deutsch
fr-fr: Français
it-it: Italiano
hu-hu: Magyar
id-id: Bahasa Indonesia
nb-no: Norsk
zh-cn: 中国
uwu: Engwish
# Game voice pack language
voice:
title: Voice pack
tooltip: You will have to manually select the new voice pack in the game
items:
en-us: English (US)
ja-jp: Japanese
ko-kr: Korean
zh-cn: Chinese
# Launcher theme
theme:
title: Theme
items:
system: System
light: Light
dark: Dark
# Discord RPC
discord:
title: Discord RPC
settings:
title: Discord RPC settings
items:
timer: Show estimated time spent
in-launcher: Launcher text
in-game: In-game text
selectIcon: Select icon
# Some buttons
buttons:
winetricks: winetricks
winecfg: winecfg
launcher: launcher folder
game: game folder
# Enhancements
enhancements:
title: Enhancements
# Enhancements related to the wine
wine:
title: Wine
items:
# HUD
hud:
title: HUD
items:
none: None
dxvk: DXVK
mangohud: MangoHUD
# Wine synchronization
winesync:
title: Wine synchronization
tooltip: ESync is a mechanism of synchronizing multi-thread operations. It can improve your game performance.
FSync is an improved version of ESync that works on specific kernel versions
items:
none: None
esync: ESync
fsync: FSync
futex2: Futex2
# AMD FSR
fsr:
title: Enable AMD FSR
tooltip: This option enables AMD FidelityFX Super Resolution (FSR)
which will scale your game resolution without losing FPS
# Wine Virtual Desktop
winevd:
title: Virtual Desktop
settings:
title: Virtual Desktop settings
items:
width: Width
height: Height
# Enhancements related to the game
game:
title: Game
items:
# GameMode
gamemode:
title: Use GameMode
tooltip:
enabled: It is a software that can improve the game performance
disabled: ⚠️ You don't have gamemode package installed
# Borderless Window
borderless_window:
title: Allow borderless window
tooltip: Removes the window borders when playing in windowed mode.
To play in a fullscreen borderless window, press alt+enter when playing on fullscreen
# Unlock FPS
fps_unlocker:
title: Unlock FPS
tooltip: This option will unlock maximum of the 60 fps limitation
# Use separate terminal window to run the game
use_terminal:
title: Use terminal
tooltip: With this option enabled launcher will run the wine command in a separate terminal window
# Enhancements related to the launcher
launcher:
title: Launcher
items:
# Delete logs
purge_logs:
# Game logs (DXVK)
game:
title: Delete DXVK logs
tooltip: With this option enabled your launcher will automatically
delete DXVK log files
# Launcher logs
launcher:
title: Delete launcher logs
tooltip: Amount of time after which launcher will delete its old log files
items:
1d: 1 day
3d: 3 days
5d: 5 days
7d: 1 week
14d: 2 weeks
never: Never
# Runners
runners:
title: Wine version
items:
recommended:
title: Show recommended only
tooltip: This option will hide unplayable wine versions
# DXVKs
dxvks:
title: DXVK
items:
recommended:
title: Show recommended only
tooltip: This option will hide old DXVK versions
# Shaders
shaders:
title: Shaders
items:
shaders:
title: Shaders
tooltip: Use Home button to toggle shaders in the game
items:
none: None
custom: Custom
author: 'Author: {author}'
no_images: No images added
not_installed: You haven't installed vkBasalt and reshade-shaders library
# Environmantal variables manager
environment:
title: Environment
items:
# Table rows
table:
name: Name
value: Value
# Table buttons
buttons:
add: Add
delete: Delete
# Notifications
notifications:
# Launcher update
launcher_update_available:
title: 'Launcher update available: {from} -> {to}'
body: You can download a new version of the launcher from the project's repository at {repository}
# Before telemetry check when iputils is not downloaded
iputils_package_required:
title: An Anime Game Launcher
body: You must have iputils installed for telemetry checking
# When telemetry servers are not disabled
telemetry_not_disabled:
title: An Anime Game Launcher
body: Telemetry servers are not disabled
# Before patch applying when xdelta3 package is not downloaded
xdelta3_package_required:
title: An Anime Game Launcher
body: You must download xdelta3 package to apply the patch
# If patch wasn't applied because of some error
patch_applying_error:
title: An Anime Game Launcher
body: Patch wasn't applied successfully. Please, check your log file to find a reason of it, or ask someone in our discord server
# Patch repositories are not available
patch_repos_unavailable:
title: An Anime Game Launcher
body: None of the patch repositories are available. You'll be able to run the game, but launcher can't be sure is it patched properly
# HDiffPatch couldn't successfully apply game files changes
game_changes_applying_error:
title: An error occurred during game updating
body: '{files} files couldn''t be updated by the hdiff patch'
# ToS violation warning window
tos_violation:
title: ToS violation warning
heading: ⚠️ Be warned
body: This launcher is an unofficial tool, in no way related to {company} nor {company_alterego}.
This tool is designed to facilitate playing {game} on Linux,
and was built with the sole purpose of installing and running the game with less hassle.
It does so by using existing components and making the experience simple for the user.
However, some components used here likely break the {company} Terms of Service for {game}.
If you are using this launcher, your player account could become identified as TOS-non-compliant by {company}/{company_alterego}.
If this happens, as your account would be disobeying TOS, {company}/{company_alterego} are free to do what they want. Including banning.
If you understand the risk of trying to play the game in an unofficial capacity, press OK and let's go researching the world of Teyvat!
buttons:
ok:
title: I understand the risk
tooltip: You really should read this text above. It's important
cancel: Cancel
discord: Our discord server
# Analytics window
analytics:
title: Yanfei's commission...
header: Participate in anonymous data collection
body:
- To count the active user base for Linux, Yanfei would like to collect your IP address everytime the game updates
- The IP address will be hashed for security purpose
actions:
share_country:
title: Share country
hint: Allow Yanfei to store the country your IP address registered in to make statistics more detailed. No other data than the country will be stored
participate: Participate
skip: Skip
skip_forever: Skip and don't ask again

View file

@ -1,17 +1,26 @@
import { Configs, promisify } from './empathize';
import { Configs } from './empathize';
import constants from './ts/Constants';
import Locales from './ts/launcher/Locales';
const systemLocale = await Locales.system();
export default new Promise<void>(async (resolve) => {
await Configs.defaults({
lang: {
launcher: await Locales.system(),
launcher: systemLocale,
voice: [
'en-us'
]
},
/**
* Game server
*
* Available options: "global" and "cn"
*/
server: systemLocale === 'zh-cn' ? 'cn' : 'global',
folders: {
/**
* Path to wine prefix

View file

@ -11,6 +11,7 @@ register('it-it', () => Locales.get('it-it'));
register('hu-hu', () => Locales.get('hu-hu'));
register('id-id', () => Locales.get('id-id'));
register('nb-no', () => Locales.get('nb-no'));
register('zh-cn', () => Locales.get('zh-cn'));
register('uwu', () => Locales.get('uwu'));
Locales.default().then((locale) => {

View file

@ -62,7 +62,7 @@
<p>{$_(`splash.phrases.${phrase}`, {
values: {
// Required by de-de locale
game: constants.placeholders.uppercase.full
game: constants.placeholders.uppercase.full.global
}
})}</p>
</main>

View file

@ -58,7 +58,7 @@
values: {
company: constants.placeholders.uppercase.company,
company_alterego: constants.placeholders.uppercase.company_alterego,
game: constants.placeholders.uppercase.full
game: constants.placeholders.uppercase.full.global
}
})}</p>

View file

@ -1,5 +1,9 @@
import type { AvailableLocales } from './launcher/Locales';
import { Configs } from '../empathize';
import Game from './Game';
declare const Neutralino;
declare const NL_CWD;
@ -142,7 +146,13 @@ class Paths
*/
public static get gameDataDir(): Promise<string>
{
return new Promise(async (resolve) => resolve(`${await this.gameDir}/${constants.placeholders.uppercase.first + constants.placeholders.uppercase.second}_Data`));
return new Promise(async (resolve) => {
const folder = await Game.server === 'global' ?
constants.placeholders.uppercase.first + constants.placeholders.uppercase.second :
constants.placeholders.uppercase.full.cn;
resolve(`${await this.gameDir}/${folder}_Data`);
});
}
/**
@ -188,7 +198,10 @@ export default class constants
/**
* Anime Game
*/
full: atob('R2Vuc2hpbiBJbXBhY3Q='),
full: {
global: atob('R2Vuc2hpbiBJbXBhY3Q='),
cn: atob('WXVhblNoZW4=')
},
/**
* anAnimeCompany
@ -215,35 +228,55 @@ export default class constants
}
};
protected static readonly api = {
key: {
global: 'gcStgarh',
cn: 'eYd89JmJ'
},
launcher_id: {
global: 10,
cn: 18
}
};
public static readonly uri = {
api: `https://sdk-os-static.${this.placeholders.lowercase.company}.com/hk4e_global/mdk/launcher/api`,
api: {
global: `https://sdk-os-static.${this.placeholders.lowercase.company}.com/hk4e_global/mdk/launcher/api`,
cn: `https://sdk-static.${this.placeholders.lowercase.company}.com/hk4e_cn/mdk/launcher/api`
},
patch: {
origin: 'https://notabug.org/Krock/dawn',
additional: 'https://dev.kaifa.ch/Maroxy/dawn'
},
launcher: 'https://gitlab.com/KRypt0n_/an-anime-game-launcher',
discord: 'https://discord.gg/ck37X6UWBp',
telemetry: [
atob('bG9nLXVwbG9hZC1vcy5taWhveW8uY29t'),
atob('b3ZlcnNlYXVzcGlkZXIueXVhbnNoZW4uY29t')
],
telemetry: {
global: [
atob('bG9nLXVwbG9hZC1vcy5taWhveW8uY29t'),
atob('b3ZlcnNlYXVzcGlkZXIueXVhbnNoZW4uY29t')
],
cn: [
atob('bG9nLXVwbG9hZC5taWhveW8uY29t'),
atob('dXNwaWRlci55dWFuc2hlbi5jb20=')
]
},
winetricks: 'https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks',
fpsunlock: {
unlocker: `https://github.com/34736384/${this.placeholders.lowercase.first}-fps-unlock/releases/download/v1.4.2/unlockfps.exe`,
bat: 'https://dev.kaifa.ch/Maroxy/an-anime-game-aur/raw/branch/fpsunlock/fpsunlock.bat'
},
launcher: 'https://gitlab.com/KRypt0n_/an-anime-game-launcher',
discord: 'https://discord.gg/ck37X6UWBp',
analytics: 'https://aagl.launcher.moe/stat/'
};
// TODO: cache drops at that dates instead of the 7 days period
/*public static readonly cacheDropDates = [
new Date('November 24, 2021').getTime(), // 2.3.0 half 1 release
new Date('December 15, 2021').getTime(), // 2.3.0 half 2 release
new Date('January 5, 2022').getTime() // 2.4.0 half 1 release
];*/
public static readonly paths = Paths;
public static readonly versionsUri: string = `${this.uri.api}/resource?key=gcStgarh&launcher_id=10`;
public static readonly backgroundUri: string = `${this.uri.api}/content?filter_adv=true&launcher_id=10&key=gcStgarh&language=`;
public static versionsUri(server: 'global' | 'cn'): string
{
return `${this.uri.api[server]}/resource?key=${this.api.key[server]}&launcher_id=${this.api.launcher_id[server]}`;
}
public static backgroundUri(server: 'global' | 'cn', lang: AvailableLocales): string
{
return `${this.uri.api[server]}/content?filter_adv=true&key=${this.api.key[server]}&launcher_id=${this.api.launcher_id[server]}&language=${lang}`;
}
}

View file

@ -7,7 +7,7 @@ import type {
import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader';
import { fetch, Domain, promisify, Downloader, Cache, Debug, Package } from '../empathize';
import { fetch, Domain, promisify, Downloader, Cache, Debug, Package, Configs } from '../empathize';
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
import constants from './Constants';
@ -25,6 +25,18 @@ class Stream extends AbstractInstaller
export default class Game
{
protected static _server: 'global' | 'cn' | null = null;
public static get server(): Promise<'global' | 'cn'>
{
return new Promise(async (resolve) => {
if (!this._server)
this._server = (await Configs.get('server')) as 'global' | 'cn';
resolve(this._server);
})
}
/**
* Get current installed game version
*
@ -64,7 +76,7 @@ export default class Game
public static getLatestData(): Promise<Data>
{
return new Promise(async (resolve, reject) => {
const response = await fetch(constants.versionsUri);
const response = await fetch(constants.versionsUri(await this.server));
if (response.ok)
{
@ -266,7 +278,7 @@ export default class Game
else
{
const pipeline = promisify({
callbacks: await constants.uri.telemetry.map((domain) => {
callbacks: await constants.uri.telemetry[await this.server].map((domain) => {
return new Promise((resolve) => {
Domain.getInfo(domain).then((info) => resolve(info.available));
});

View file

@ -112,7 +112,11 @@ export default class Launcher
{
locale.set(record.pop().data.locale);
Background.get().then((uri) => document.getElementsByClassName('background')[0]!.setAttribute('src', uri));
Background.get().then((uri) => {
if (uri)
document.getElementsByClassName('background')[0]!.setAttribute('src', uri);
});
this.getSocial().then((uri) => document.getElementById('social-iframe')!.setAttribute('src', uri));
}
}
@ -140,6 +144,8 @@ export default class Launcher
/**
* Get launcher social buttons uri
*
* TODO: Chinese URI
*/
public getSocial(): Promise<string>
{

View file

@ -343,7 +343,7 @@ export default class Patch
else
{
fetch(`${patchUri}/raw/master/${version.replaceAll('.', '')}/patch_files/unityplayer_patch_os.vcdiff`, this.fetchTimeout)
.then((response) => {
.then(async (response) => {
// Return an error if patch's server is unavailable
if (response.status === null)
rejectOutput(new Error(`${source} patch repository is unreachable`));
@ -356,7 +356,8 @@ export default class Patch
version: version,
state: 'preparation',
applied: false,
source: source
source: source,
server: await Game.server
});
}
@ -370,7 +371,7 @@ export default class Patch
rejectOutput(new Error(`${source} patch repository is unreachable`));
else patcherResponse.body(this.fetchTimeout)
.then((response) => {
.then(async (response) => {
// Return an error if patch's server is unavailable
if (response === '')
rejectOutput(new Error(`${source} patch repository is unreachable`));
@ -386,22 +387,28 @@ export default class Patch
version: version,
state: response.includes(stableMark) ? 'stable' : 'testing',
applied: false,
source: source
source: source,
server: await Game.server
};
const originalPlayer = /if \[ "\${sum}" == "([a-z0-9]{32})" \]; then/mg.exec(response);
const hashesMatches = [...response.matchAll(/if \[ "\${sum}" == "([a-z0-9]{32})" \]; then/mg)];
// If we could get original UnityPlayer.dll hash - then we can
// compare it with actual UnityPlayer.dll hash and say whether the patch
// was applied or not
if (originalPlayer !== null)
if (hashesMatches.length === 2)
{
const originalPlayer = {
global: hashesMatches[0][1],
cn: hashesMatches[1][1]
}[patchInfo.server];
constants.paths.gameDir.then((gameDir) => {
Neutralino.filesystem.readBinaryFile(`${gameDir}/UnityPlayer.dll`)
.then((currPlayer: ArrayBuffer) => {
patchInfo.applied = md5(currPlayer) != originalPlayer[1];
patchInfo.applied = md5(currPlayer) != originalPlayer;
resolveOutput(patchInfo, originalPlayer[1]);
resolveOutput(patchInfo, originalPlayer);
})
.catch(() => resolveOutput(patchInfo));
});

View file

@ -2,19 +2,41 @@ import { fetch } from '../../empathize';
import constants from '../Constants';
import Locales from './Locales';
import Game from '../Game';
export default class Background
{
/**
* Get background uri
*
* @return null if uri is not available
*/
public static get(): Promise<string>
public static get(): Promise<string|null>
{
return new Promise(async (resolve) => {
fetch(constants.backgroundUri + Locales.fallback((await Locales.default()) ?? 'en-us'))
.then((header) => header.body().then((body) => {
resolve(JSON.parse(body).data.adv.background);
}));
const server = await Game.server;
const locale = Locales.fallback((await Locales.default()) ?? 'en-us');
const tryToFetch = (server: 'global' | 'cn'): Promise<string|null> => {
return new Promise((resolve) => {
fetch(constants.backgroundUri(server, locale))
.then((header) => header.body().then((body) => {
const data = JSON.parse(body);
if (data.message !== 'OK' || data.data.adv === null)
{
if (server === 'global')
resolve(null);
else tryToFetch('global').then(resolve);
}
else resolve(data.data.adv.background);
}));
});
};
tryToFetch(server).then(resolve);
});
}

View file

@ -11,6 +11,7 @@ type AvailableLocales =
// Supported by the game's API
| 'en-us' | 'ru-ru' | 'es-es'
| 'de-de' | 'fr-fr' | 'id-id'
| 'zh-cn'
// Unsupported by the game's API
| 'it-it' | 'hu-hu' | 'uwu'
@ -24,7 +25,7 @@ export default class Locales
* List of locales supported by the game's API
*/
public static readonly supportedLocales: AvailableLocales[] = [
'en-us', 'ru-ru', 'es-es', 'de-de', 'fr-fr', 'id-id'
'en-us', 'ru-ru', 'es-es', 'de-de', 'fr-fr', 'id-id', 'zh-cn'
];
/**

View file

@ -247,7 +247,7 @@ export default (launcher: Launcher): Promise<void> => {
// Game was closed
else
{
const stopTime = Date.now();
// const stopTime = Date.now();
Windows.current.show();
Windows.current.center(1280, 700);

View file

@ -19,6 +19,11 @@ type PatchInfo = {
* Source where this info was got from
*/
source?: 'origin' | 'additional';
/**
* Used game server
*/
server: 'global' | 'cn';
};
export type {