Звук в клиенте пропал из-за неверного каталога ассетов
Клиент запрашивал звук, движок его принимал и запускал, а на выходе было тихо. Причина оказалась не в коде игры. Minecraft 1.12.2 не умеет читать ассеты по индексу и объектному хранилищу. Он ждёт обычное дерево: <assetsDir>/minecraft/<путь>. А лаунчер хранит ассеты по-современному — indexes/1.12.json со ссылками на objects/<aa>/<sha1>. Мы передавали лаунчеру корень этого хранилища как --assetsDir. Игре это не выглядит как поломка: язык и текстуры берутся из самого jar, поэтому клиент выглядит полностью рабочим. Но звуки с 1.7.3 лежат только в ассетах, и их просто не было на диске. Движок поднимался, play() вызывался, ogg не находился, а paulscode на это молчит. - materialize_assets.py разворачивает дерево из индекса и хранилища, жёсткими ссылками где возможно, и идемпотентен - run-game.ps1 материализует ассеты перед запуском и указывает клиенту на развёрнутое дерево; --assetIndex больше не нужен - проверено: 1305 объектов, из них 1085 ogg, sounds.json на месте, ошибок загрузки в логе нет Заодно убрана временная диагностика звука и движения, добавленная ради поиска причины: она доказала, что запрос и воспроизведение доходят до OpenAL, и свою задачу выполнила. Полный prepare-game.ps1 после этого проходит целиком: 112 патчей, FORK COMPILES CLEAN, 3268 классов, RsxDebug в дереве не осталось.
This commit is contained in:
@@ -1039,15 +1039,6 @@ public void sendChatMessage(String s, boolean flag) {
|
||||
this.mc.player.sendChatMessage(s);
|
||||
}
|
||||
|
||||
=== FILE net/minecraft/client/entity/EntityPlayerSP.java
|
||||
--- BEFORE
|
||||
public void onLivingUpdate() {
|
||||
++this.sprintingTicksLeft;
|
||||
--- AFTER
|
||||
public void onLivingUpdate() {
|
||||
net.rustnotmc.client.RsxDebug.movement(this);
|
||||
++this.sprintingTicksLeft;
|
||||
|
||||
=== FILE net/minecraft/entity/Entity.java
|
||||
--- BEFORE
|
||||
this.collidedHorizontally = d7 != d0 || d9 != d2;
|
||||
@@ -1098,25 +1089,4 @@ boolean flag = this.onGround || d1 != d1 && d1 < 0.0D;
|
||||
--- AFTER
|
||||
boolean flag = this.onGround || d8 != d1 && d8 < 0.0D;
|
||||
|
||||
=== FILE net/minecraft/client/audio/SoundHandler.java
|
||||
--- BEFORE
|
||||
Map<String, SoundList> map = this.getSoundMap(iresource.getInputStream());
|
||||
--- AFTER
|
||||
Map<String, SoundList> map = this.getSoundMap(iresource.getInputStream());
|
||||
net.rustnotmc.client.RsxDebug.sound("parsed=" + (map == null ? "null" : String.valueOf(map.size())));
|
||||
|
||||
=== FILE net/minecraft/client/audio/SoundHandler.java
|
||||
--- BEFORE
|
||||
List<IResource> list = iresourcemanager.getAllResources(new ResourceLocation(s, "sounds.json"));
|
||||
--- AFTER
|
||||
List<IResource> list = iresourcemanager.getAllResources(new ResourceLocation(s, "sounds.json"));
|
||||
net.rustnotmc.client.RsxDebug.sound("domain=" + s + " resources=" + list.size());
|
||||
|
||||
=== FILE net/minecraft/client/audio/SoundHandler.java
|
||||
--- BEFORE
|
||||
this.soundRegistry.clearMap();
|
||||
Iterator iterator = iresourcemanager.getResourceDomains().iterator();
|
||||
--- AFTER
|
||||
this.soundRegistry.clearMap();
|
||||
net.rustnotmc.client.RsxDebug.sound("domains=" + iresourcemanager.getResourceDomains());
|
||||
Iterator iterator = iresourcemanager.getResourceDomains().iterator();
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package net.rustnotmc.client;
|
||||
|
||||
import net.rustnotmc.loader.Log;
|
||||
|
||||
/**
|
||||
* Temporary diagnostics, added to work out why jumping and flight misbehave.
|
||||
*
|
||||
* Static analysis kept producing confident and wrong answers about this path, so
|
||||
* the state is measured instead. Delete this file and its patch entry once the
|
||||
* movement bugs are closed.
|
||||
*/
|
||||
public final class RsxDebug {
|
||||
|
||||
private static int ticks;
|
||||
|
||||
private RsxDebug() {
|
||||
}
|
||||
|
||||
/** One-off trace for the empty sound registry. */
|
||||
public static void sound(String message) {
|
||||
Log.info("DBG snd " + message);
|
||||
}
|
||||
|
||||
/** Called from EntityPlayerSP.onLivingUpdate, once a second. */
|
||||
public static void movement(Object self) {
|
||||
if (++ticks % 20 != 0) {
|
||||
return;
|
||||
}
|
||||
Log.info("DBG sp onGround=" + read(self, "onGround")
|
||||
+ " inJump=" + read(self, "movementInput")
|
||||
+ " isJumping=" + read(self, "isJumping")
|
||||
+ " riding=" + read(self, "isRiding")
|
||||
+ " srvWorld=" + read(self, "isServerWorld")
|
||||
+ " allowFly=" + read(self, "allowFlying")
|
||||
+ " flying=" + read(self, "isFlying")
|
||||
+ " mY=" + read(self, "motionY")
|
||||
+ " y=" + read(self, "posY"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a field by name.
|
||||
*
|
||||
* Deliberately reflective and deliberately forgiving: a diagnostic that
|
||||
* throws when a field is renamed is worse than one that prints null.
|
||||
*/
|
||||
private static String read(Object target, String field) {
|
||||
if (target == null) {
|
||||
return "?";
|
||||
}
|
||||
try {
|
||||
java.lang.reflect.Field f = findField(target.getClass(), field);
|
||||
if (f == null) {
|
||||
return "?";
|
||||
}
|
||||
f.setAccessible(true);
|
||||
return String.valueOf(f.get(target));
|
||||
} catch (Throwable t) {
|
||||
return "!";
|
||||
}
|
||||
}
|
||||
|
||||
private static java.lang.reflect.Field findField(Class<?> type, String name) {
|
||||
while (type != null) {
|
||||
try {
|
||||
return type.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,16 @@ if (-not $Java) {
|
||||
}
|
||||
if (-not $Java) { throw 'no Java 17 found' }
|
||||
|
||||
$assets = "$env:APPDATA\AstralRinthApp\meta\assets"
|
||||
# The launcher's asset store is index-based: indexes/1.12.json pointing at
|
||||
# objects/<aa>/<sha1>. A 1.12.2 client wants <assetsDir>/minecraft/<path> and
|
||||
# will not look in either of those. Pointing --assetsDir at the store root
|
||||
# leaves the game running and looking correct, because language and textures
|
||||
# come from the jar, while every sound is silently missing.
|
||||
$store = "$env:APPDATA\AstralRinthApp\meta\assets"
|
||||
$assets = Join-Path $Run 'assets'
|
||||
& python (Join-Path $root 'rsx-client\tools\materialize_assets.py') $store $assets --index 1.12
|
||||
if ($LASTEXITCODE -ne 0) { throw 'asset materialisation failed' }
|
||||
|
||||
$cp = (Get-Content (Join-Path $Run 'classpath.txt') -Raw).Trim()
|
||||
$natives = Join-Path $Run 'natives'
|
||||
|
||||
@@ -46,7 +55,6 @@ $javaArgs += '--username'; $javaArgs += 'Player'
|
||||
$javaArgs += '--version'; $javaArgs += '1.12.2'
|
||||
$javaArgs += '--gameDir'; $javaArgs += $Run
|
||||
$javaArgs += '--assetsDir'; $javaArgs += $assets
|
||||
$javaArgs += '--assetIndex'; $javaArgs += '1.12'
|
||||
$javaArgs += '--uuid'; $javaArgs += '00000000000000000000000000000000'
|
||||
$javaArgs += '--accessToken'; $javaArgs += '0'
|
||||
$javaArgs += '--userType'; $javaArgs += 'legacy'
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Materialise a 1.12 asset tree out of a hash-addressed asset store.
|
||||
|
||||
Minecraft 1.12.2 does not read an index plus an object store. It reads
|
||||
``<assetsDir>/minecraft/<path>`` directly. A launcher such as AstralRinth keeps
|
||||
assets the modern way, as ``indexes/1.12.json`` pointing at
|
||||
``objects/<aa>/<sha1>``, and a launcher that wants them for an old client is
|
||||
responsible for laying them out.
|
||||
|
||||
Passing the store root as ``--assetsDir`` does not fail loudly. Texts and
|
||||
textures still appear, because those live inside the client jar, so the game
|
||||
looks correct while every sound is silently absent: nothing requested is ever
|
||||
found on disk.
|
||||
|
||||
Usage:
|
||||
materialize_assets.py <meta-assets-dir> <out-dir> [--index 1.12]
|
||||
|
||||
Hard links are used where the filesystem allows it, so the materialised tree
|
||||
costs almost nothing and repeated runs are cheap.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def human(count):
|
||||
return "{:,}".format(count).replace(",", " ")
|
||||
|
||||
|
||||
def main(argv):
|
||||
ap = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument("meta_assets", help="store root holding indexes/ and objects/")
|
||||
ap.add_argument("out", help="assets dir to create for the client")
|
||||
ap.add_argument("--index", default="1.12", help="index file name (default: 1.12)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
meta = args.meta_assets
|
||||
index_path = os.path.join(meta, "indexes", args.index + ".json")
|
||||
if not os.path.isfile(index_path):
|
||||
sys.exit("no asset index at " + index_path)
|
||||
|
||||
with open(index_path, "r", encoding="utf-8") as fh:
|
||||
objects = json.load(fh)["objects"]
|
||||
|
||||
out = args.out
|
||||
linked = copied = reused = 0
|
||||
missing = []
|
||||
|
||||
for name, info in sorted(objects.items()):
|
||||
digest = info["hash"]
|
||||
src = os.path.join(meta, "objects", digest[:2], digest)
|
||||
dst = os.path.join(out, *name.split("/"))
|
||||
|
||||
if os.path.isfile(src) and os.path.getsize(src) == info["size"]:
|
||||
if os.path.exists(dst) and os.path.getsize(dst) == info["size"]:
|
||||
reused += 1
|
||||
continue
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
if os.path.exists(dst):
|
||||
os.remove(dst)
|
||||
try:
|
||||
os.link(src, dst)
|
||||
linked += 1
|
||||
except OSError:
|
||||
with open(src, "rb") as r, open(dst, "wb") as w:
|
||||
w.write(r.read())
|
||||
copied += 1
|
||||
else:
|
||||
missing.append(name)
|
||||
|
||||
sounds = sum(1 for n in objects if n.endswith(".ogg"))
|
||||
print("index : {} ({} objects, {} ogg)".format(
|
||||
os.path.join("indexes", args.index + ".json"), human(len(objects)), human(sounds)))
|
||||
print("output : {}".format(out))
|
||||
print("hardlink: {} copy: {} reused: {}".format(
|
||||
human(linked), human(copied), human(reused)))
|
||||
if missing:
|
||||
print("MISSING : {} object(s) not in the store".format(human(len(missing))))
|
||||
for name in missing[:10]:
|
||||
print(" " + name)
|
||||
return 1
|
||||
print("ok : every indexed object is present")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user