Skip to content

Commit bc9069c

Browse files
PhoenixVXDexrnZacAttack
authored andcommitted
chore: move sources from libLodestone
1 parent 17869a2 commit bc9069c

4 files changed

Lines changed: 281 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
cmake_minimum_required(VERSION VERSION 3.30)
2+
project(JavaObjectStreams VERSION 1.0.0)
3+
4+
set(CMAKE_CXX_STANDARD 20)
5+
6+
if (NOT TARGET bio)
7+
include(FetchContent)
8+
FetchContent_Declare(
9+
bio
10+
GIT_REPOSITORY https://codeberg.org/Dexrn/libBIO.git
11+
GIT_TAG 4.0.0-dev.5
12+
)
13+
14+
FetchContent_MakeAvailable(bio)
15+
endif ()
16+
17+
set(FILES
18+
include/JavaObject/JavaSerializedClassParser.h
19+
src/JavaSerializedClassParser.cpp
20+
)
21+
22+
add_library(JavaObjectStreams SHARED ${FILES})
23+
target_include_directories(JavaObjectStreams
24+
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
25+
)
26+
27+
target_link_libraries(JavaObjectStreams PUBLIC bio-cpp20)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# JavaObjectStreams
22
Serialization and Deserialization which matches Java's Object streams
3+
4+
This allows for deserializing ObjectOutputStream-serialized files
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/** @file JavaSerializedClassParser.h
2+
*
3+
* @author Zero_DSRS_VX
4+
* @date 3/22/26
5+
*
6+
* @device mac-8
7+
*
8+
* @copyright Copyright (c) 2026 Team Lodestone
9+
* @license This project is licensed under the MIT license, see the LICENSE file for details.
10+
*/
11+
#ifndef JAVAOBJECTSTREAMS_JAVASERIALIZEDCLASSPARSER_H
12+
#define JAVAOBJECTSTREAMS_JAVASERIALIZEDCLASSPARSER_H
13+
#include <istream>
14+
#include <variant>
15+
16+
#include "BinaryIO/stream/BinaryOutputStream.h"
17+
18+
namespace bio::stream {
19+
class BinaryInputStream;
20+
}
21+
22+
namespace javaobject {
23+
class JavaSerializedClassParser;
24+
class SerializedClass;
25+
class SerializedField;
26+
27+
struct JavaObject {
28+
SerializedClass& m_class;
29+
};
30+
using JavaArray = std::vector<JavaObject>;
31+
32+
// DO NOT RE-ORDER VARIANTS
33+
using JavaValue = std::variant<int64_t, double, int32_t, float, bool, JavaArray, std::shared_ptr<JavaObject>, std::string>;
34+
35+
class SerializedClass {
36+
public:
37+
std::string m_className;
38+
39+
std::vector<SerializedField> m_fields;
40+
};
41+
42+
class SerializedField {
43+
public:
44+
const SerializedClass& m_class;
45+
std::string name;
46+
std::string desc;
47+
JavaValue value;
48+
49+
explicit SerializedField(const SerializedClass& clazz, const std::string &name, const std::string &desc) : m_class(clazz), name(name), desc(desc) {}
50+
51+
static SerializedField parseFieldEntry(const SerializedClass& clazz, bio::stream::BinaryInputStream &strm);
52+
static std::string parseSignature(const SerializedClass& clazz, char type, bio::stream::BinaryInputStream &strm);
53+
static JavaValue readFieldValue(JavaSerializedClassParser& parser, const SerializedField &field, bio::stream::BinaryInputStream &strm);
54+
};
55+
56+
class JavaSerializedClassParser {
57+
bio::stream::BinaryInputStream& m_stream;
58+
std::vector<SerializedClass> m_serializedClasses;
59+
public:
60+
explicit JavaSerializedClassParser(bio::stream::BinaryInputStream &strm) : m_stream(strm) {};
61+
62+
std::vector<SerializedClass> parseAllEntries();
63+
SerializedClass parseEntry();
64+
};
65+
} // lodestone::minecraft::common::java::classic::minev3
66+
67+
#endif //JAVAOBJECTSTREAMS_JAVASERIALIZEDCLASSPARSER_H

src/JavaSerializedClassParser.cpp

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/** @file JavaSerializedClassParser.cpp
2+
*
3+
* @author Zero_DSRS_VX
4+
* @date 3/22/26
5+
*
6+
* @device mac-8
7+
*
8+
* @copyright Copyright (c) 2026 Team Lodestone
9+
* @license This project is licensed under the MIT license, see the LICENSE file for details.
10+
*/
11+
12+
#include "JavaObject/JavaSerializedClassParser.h"
13+
#include "BinaryIO/stream/BinaryInputStream.h"
14+
15+
namespace javaobject {
16+
SerializedField
17+
SerializedField::parseFieldEntry(const SerializedClass &clazz, bio::stream::BinaryInputStream &strm) {
18+
// Parse field entry
19+
const char type = strm.readByte();
20+
auto descriptor = std::string(1, type);
21+
22+
const std::string name = strm.readStringWithLength<char>(bio::util::ByteOrder::BIG,
23+
bio::util::string::StringLengthEncoding::LENGTH_PREFIX);
24+
25+
// Check if descriptor is object or array
26+
if (type == 'L' || type == '[') {
27+
descriptor = parseSignature(clazz, type, strm);
28+
}
29+
return SerializedField{clazz, name, descriptor};
30+
}
31+
32+
std::string SerializedField::parseSignature(const SerializedClass &clazz, const char type,
33+
bio::stream::BinaryInputStream &strm) {
34+
auto descriptor = std::string(1, type);
35+
36+
// Check if descriptor is object or array
37+
if (type == 'L' || type == '[') {
38+
char typeCode = strm.readByte();
39+
if (typeCode == '[') {
40+
// Read primitive array
41+
const char arrayType = strm.readByte();
42+
if (arrayType != 'L') {
43+
descriptor += arrayType;
44+
} else {
45+
descriptor += parseSignature(clazz, arrayType, strm);
46+
}
47+
} else if (typeCode == 't') {
48+
descriptor = strm.readStringWithLength<char>(bio::util::ByteOrder::BIG,
49+
bio::util::string::StringLengthEncoding::LENGTH_PREFIX);
50+
} else if (typeCode == 'q') {
51+
// Type is the same as the field before this one
52+
if (!clazz.m_fields.empty()) {
53+
descriptor = clazz.m_fields.back().desc;
54+
55+
// skip a few bytes
56+
// TODO: Figure out if this means anything
57+
for (int i = 0; i < 4; i++) {
58+
strm.readByte();
59+
}
60+
}
61+
} else {
62+
// LOG_DEBUG("Unrecognized type code!");
63+
}
64+
}
65+
return descriptor;
66+
}
67+
68+
JavaValue SerializedField::readFieldValue(JavaSerializedClassParser& parser, const SerializedField &field, bio::stream::BinaryInputStream &strm) {
69+
const auto descriptor = field.desc;
70+
if (descriptor == "B") {
71+
return JavaValue { strm.readSignedByte() };
72+
}
73+
if (descriptor == "Z") {
74+
return JavaValue { (strm.readByte() != 0) };
75+
}
76+
if (descriptor == "C") {
77+
return JavaValue { static_cast<char>(strm.readByte()) };
78+
}
79+
if (descriptor == "D") {
80+
return JavaValue { strm.readBE<double>() };
81+
}
82+
if (descriptor == "F") {
83+
return JavaValue { strm.readBE<float>() };
84+
}
85+
if (descriptor == "I") {
86+
// Java always stores integers as signed
87+
// C++ uses two's-complements when reading the integer in
88+
// which means we need to shift this down to get the
89+
// correct sign and value.
90+
return JavaValue { static_cast<int32_t>(strm.readBE<uint32_t>() >> 8) };
91+
}
92+
if (descriptor == "J") {
93+
return JavaValue { static_cast<int64_t>(strm.readBE<uint64_t>() >> 8) };
94+
}
95+
if (descriptor.starts_with("L")) {
96+
if (descriptor == "Ljava/lang/String;") {
97+
return JavaValue { std::string(strm.readStringWithLength<char>(bio::util::ByteOrder::BIG, bio::util::string::StringLengthEncoding::LENGTH_PREFIX)) };
98+
}
99+
strm.seekRelative(-1);
100+
int offset = strm.getOffset();
101+
// Object!
102+
SerializedClass clazz = parser.parseEntry();
103+
return JavaValue { std::make_shared<JavaObject>(clazz)};
104+
}
105+
if (descriptor.starts_with("[")) {
106+
// Array!
107+
return readFieldValue(parser, field, strm);
108+
}
109+
110+
// LOG_DEBUG("Descriptor not yet implemented!");
111+
return {};
112+
}
113+
114+
std::vector<SerializedClass> JavaSerializedClassParser::parseAllEntries() {
115+
std::vector<SerializedClass> result;
116+
if (!this->m_stream.getStream().eof()) {
117+
SerializedClass clazz = this->parseEntry();
118+
}
119+
120+
return result;
121+
}
122+
123+
SerializedClass JavaSerializedClassParser::parseEntry() {
124+
SerializedClass result;
125+
126+
if (const uint16_t classMagic = this->m_stream.readBE<uint16_t>(); classMagic != 0x7372) {
127+
return result;
128+
}
129+
130+
// Read class name
131+
result.m_className = this->m_stream.readStringWithLength<char>(bio::util::ByteOrder::BIG,
132+
bio::util::string::StringLengthEncoding::LENGTH_PREFIX);
133+
this->m_stream.seekRelative(8);
134+
135+
// TODO: Read fields from serialized class object
136+
this->m_stream.seekRelative(2); // TODO: Look at this more
137+
138+
const int8_t numberOfFields = this->m_stream.readSignedByte();
139+
140+
result.m_fields.clear();
141+
142+
for (int i = 0; i < numberOfFields; i++) {
143+
SerializedField field = SerializedField::parseFieldEntry(result, this->m_stream);
144+
result.m_fields.push_back(field);
145+
}
146+
147+
char tcEndBlockData = this->m_stream.readByte();
148+
if (tcEndBlockData != 0x78) {
149+
// LOG_ERROR(
150+
// "TCEndBlockData was not found after reading fields! Stream is either corrupted or signature parsing failed!");
151+
}
152+
char tcSuperclassDesc = this->m_stream.readByte();
153+
if (tcSuperclassDesc == 0x72) {
154+
// Superclass was specified
155+
// TODO: Implement reading of superclass
156+
157+
tcEndBlockData = this->m_stream.readByte();
158+
if (tcEndBlockData != 0x78) {
159+
// LOG_ERROR(
160+
// "TCEndBlockData was not found after reading superclass! Stream is either corrupted or superclass could not be parsed!");
161+
}
162+
163+
// TODO: Remove this hack:
164+
// This will skip over the superclass declaration if it exists
165+
while (true) {
166+
if (this->m_stream.readByte() == 0x78) {
167+
break;
168+
}
169+
}
170+
} else if (tcSuperclassDesc == 0x70) {
171+
// Superclass was null
172+
}
173+
// Skip one byte
174+
this->m_stream.seekRelative(1);
175+
176+
// Read field values
177+
for (int i = 0; i < numberOfFields; i++) {
178+
SerializedField& field = result.m_fields[i];
179+
field.value = field.readFieldValue(*this, field, this->m_stream);
180+
}
181+
182+
this->m_serializedClasses.push_back(result);
183+
return result;
184+
}
185+
} // lodestone::minecraft::common::java::classic::minev3

0 commit comments

Comments
 (0)