Skip to content

Commit

Permalink
RTTI: Implement reading/writing of HZD's LocalizedSimpleSoundResource
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadelessFox committed Mar 7, 2024
1 parent 39cfe79 commit eba1366
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,10 @@ public void read(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer,

@Override
public void write(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer, @NotNull RTTIObject object) {
int mask = 0;

final RTTIObject[] dataSources = object.objs("DataSources");
final RTTIObject wave = object.obj("WaveData");

final List<RTTIEnum.Constant> supportedLanguages = getLanguages(registry);
final List<RTTIEnum.Constant> usedLanguages = Arrays.stream(dataSources)
.map(RTTIObject::<Entry>cast)
.map(entry -> entry.language)
.toList();

for (int i = 0; i < supportedLanguages.size(); i++) {
if (usedLanguages.contains(supportedLanguages.get(i))) {
mask |= 1 << i;
}
}

buffer.putShort((short) mask);

buffer.putShort((short) computeMask(registry, dataSources));
buffer.put(wave.bool("IsStreaming") ? (byte) 1 : 0);
buffer.put(wave.bool("UseVBR") ? (byte) 1 : 0);
buffer.put((byte) wave.<RTTITypeEnum.Constant>get("EncodingQuality").value());
Expand All @@ -92,6 +77,23 @@ public void write(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer
}
}

private static int computeMask(@NotNull RTTITypeRegistry registry, RTTIObject[] dataSources) {
int mask = 0;

final List<RTTIEnum.Constant> supportedLanguages = getLanguages(registry);
final List<RTTIEnum.Constant> usedLanguages = Arrays.stream(dataSources)
.map(RTTIObject::<Entry>cast)
.map(entry -> entry.language)
.toList();

for (int i = 0; i < supportedLanguages.size(); i++) {
if (usedLanguages.contains(supportedLanguages.get(i))) {
mask |= 1 << i;
}
}
return mask;
}

@Override
public int getSize(@NotNull RTTITypeRegistry registry, @NotNull RTTIObject object) {
return 23 + Arrays.stream(object.objs("DataSources"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package com.shade.decima.model.rtti.messages.hzd;

import com.shade.decima.model.base.GameType;
import com.shade.decima.model.rtti.RTTIClass;
import com.shade.decima.model.rtti.RTTIEnum;
import com.shade.decima.model.rtti.Type;
import com.shade.decima.model.rtti.messages.MessageHandler;
import com.shade.decima.model.rtti.messages.MessageHandlerRegistration;
import com.shade.decima.model.rtti.objects.RTTIObject;
import com.shade.decima.model.rtti.registry.RTTITypeRegistry;
import com.shade.decima.model.rtti.types.RTTITypeEnum;
import com.shade.decima.model.rtti.types.hzd.HZDDataSource;
import com.shade.decima.model.rtti.types.java.HwDataSource;
import com.shade.decima.model.rtti.types.java.RTTIField;
import com.shade.platform.model.util.BufferUtils;
import com.shade.util.NotNull;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

@MessageHandlerRegistration(message = "MsgReadBinary", types = {
@Type(name = "LocalizedSimpleSoundResource", game = GameType.HZD)
})
public class HZDLocalizedSimpleSoundResourceHandler implements MessageHandler.ReadBinary {
@Override
public void read(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer, @NotNull RTTIObject object) {
final String location = BufferUtils.getString(buffer, buffer.getInt());
final int mask = buffer.getShort() & 0xffff;
final byte size = buffer.get();
final byte flags = buffer.get();
final List<RTTIObject> entries = new ArrayList<>();

if (size != 28) {
throw new IllegalStateException("Entry size mismatch: " + size + " != 28");
}

final RTTIObject wave = registry.<RTTIClass>find("WaveResource").instantiate();
wave.set("IsStreaming", (flags & 1) != 0);
wave.set("UseVBR", (flags & 2) != 0);
wave.set("EncodingQuality", registry.<RTTITypeEnum>find("EWaveDataEncodingQuality").valueOf((flags >> 2 & 15)));
wave.set("FrameSize", buffer.getShort());
wave.set("Encoding", registry.<RTTITypeEnum>find("EWaveDataEncoding").valueOf(buffer.get() & 0xff));
wave.set("ChannelCount", buffer.get());
wave.set("SampleRate", buffer.getInt());
wave.set("BitsPerSample", buffer.getShort());
wave.set("BitsPerSecond", buffer.getInt());
wave.set("BlockAlignment", buffer.getShort());
wave.set("FormatTag", buffer.getShort());

int shift = 0;
for (RTTIEnum.Constant language : getLanguages(registry)) {
if ((mask & (1 << shift)) != 0) {
entries.add(Entry.read(registry, buffer, language, location));
}
shift += 1;
}

object.set("Location", location);
object.set("WaveData", wave);
object.set("DataSources", entries.toArray(RTTIObject[]::new));
}

@Override
public void write(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer, @NotNull RTTIObject object) {
final var location = object.str("Location").getBytes(StandardCharsets.UTF_8);
final var wave = object.obj("WaveData");
final var dataSources = object.objs("DataSources");

buffer.putInt(location.length);
buffer.put(location);
buffer.putShort((short) computeMask(registry, dataSources));
buffer.put((byte) 28);
buffer.put((byte) computeFlags(wave));
buffer.putShort(wave.i16("FrameSize"));
buffer.put((byte) wave.<RTTITypeEnum.Constant>get("Encoding").value());
buffer.put(wave.i8("ChannelCount"));
buffer.putInt(wave.i32("SampleRate"));
buffer.putShort(wave.i16("BitsPerSample"));
buffer.putInt(wave.i32("BitsPerSecond"));
buffer.putShort(wave.i16("BlockAlignment"));
buffer.putShort(wave.i16("FormatTag"));

for (RTTIObject dataSource : dataSources) {
dataSource.<Entry>cast().write(registry, buffer);
}
}

@Override
public int getSize(@NotNull RTTITypeRegistry registry, @NotNull RTTIObject object) {
return 26
+ object.str("Location").getBytes(StandardCharsets.UTF_8).length
+ object.objs("DataSources").length * Entry.getSize();
}

@NotNull
@Override
public Component[] components(@NotNull RTTITypeRegistry registry) {
return new Component[]{
new Component("Location", registry.find("String")),
new Component("WaveData", registry.find("WaveResource")),
new Component("DataSources", registry.find(Entry[].class))
};
}

@NotNull
private static List<RTTIEnum.Constant> getLanguages(@NotNull RTTITypeRegistry registry) {
return Arrays.stream(registry.<RTTIEnum>find("ELanguage").values())
.filter(language -> (getFlags(language) & 2) != 0)
.toList();
}

private static int computeFlags(@NotNull RTTIObject wave) {
int flags = 0;
flags |= wave.bool("IsStreaming") ? 1 : 0;
flags |= wave.bool("UseVBR") ? 2 : 0;
flags |= wave.<RTTITypeEnum.Constant>get("EncodingQuality").value() << 2;
return flags;
}

private static int computeMask(@NotNull RTTITypeRegistry registry, @NotNull RTTIObject[] dataSources) {
int mask = 0;

final List<RTTIEnum.Constant> supportedLanguages = getLanguages(registry);
final List<RTTIEnum.Constant> usedLanguages = Arrays.stream(dataSources)
.map(RTTIObject::<Entry>cast)
.map(entry -> entry.language)
.toList();

for (int i = 0; i < supportedLanguages.size(); i++) {
if (usedLanguages.contains(supportedLanguages.get(i))) {
mask |= 1 << i;
}
}
return mask;
}

private static int getFlags(@NotNull RTTITypeEnum.Constant language) {
// See sub_7FF6BFD65BD0
return switch (language.value()) {
case 1 -> 7;
case 2, 3, 4, 5, 7, 10, 11, 16, 17, 18, 20 -> 3;
default -> 1;
};
}

public static class Entry {
@RTTIField(type = @Type(name = "ELanguage"))
public RTTITypeEnum.Constant language;
@RTTIField(type = @Type(type = HwDataSource.class))
public RTTIObject dataSource;
@RTTIField(type = @Type(name = "uint64"))
public long unk;

@NotNull
public static RTTIObject read(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer, @NotNull RTTIEnum.Constant language, @NotNull String location) {
final var unk1 = buffer.getInt();
final var unk2 = buffer.getLong();
final var offset = buffer.getLong();
final var length = buffer.getLong();
assert unk1 == length;

final var dataSource = new HZDDataSource();
dataSource.location = "%s.%s.stream".formatted(location, language.name().toLowerCase(Locale.ROOT));
dataSource.offset = offset;
dataSource.length = length;

final var object = new Entry();
object.language = language;
object.dataSource = new RTTIObject(registry.find(HZDDataSource.class), dataSource);
object.unk = unk2;

return new RTTIObject(registry.find(Entry.class), object);
}

public void write(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer) {
final HZDDataSource dataSource = this.dataSource.cast();

buffer.putInt((int) dataSource.length);
buffer.putLong(unk);
buffer.putLong(dataSource.offset);
buffer.putLong(dataSource.length);
}

public static int getSize() {
return 28;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ public RTTITypeClass(@NotNull String name, int version, int flags) {
@NotNull
@Override
public RTTIObject instantiate() {
final RTTIClass.Message<MessageHandler.ReadBinary> message = getMessage("MsgReadBinary");
final MessageHandler.ReadBinary handler = message != null ? message.getHandler() : null;

if (handler != null) {
throw new IllegalStateException("Can't instantiate a class with the 'MsgReadBinary' message");
}

final Map<RTTIClass.Field<?>, Object> values = new HashMap<>();

for (FieldWithOffset info : getOrderedFields()) {
Expand Down Expand Up @@ -108,8 +101,9 @@ public void write(@NotNull RTTITypeRegistry registry, @NotNull ByteBuffer buffer

handler.write(registry, buffer, object);

if (buffer.position() - position != size) {
throw new IllegalStateException("The size of the written data doesn't match the expected size");
final int written = buffer.position() - position;
if (written != size) {
throw new IllegalStateException("The size of the written data doesn't match the expected size (expected: " + size + ", actual: " + written + ")");
}
} else {
buffer.put(object.<byte[]>get(RTTITypeClass.EXTRA_DATA_FIELD));
Expand Down

0 comments on commit eba1366

Please sign in to comment.