diff --git a/Common/StreamReader.go b/Common/StreamReader.go new file mode 100644 index 00000000..6b647101 --- /dev/null +++ b/Common/StreamReader.go @@ -0,0 +1,51 @@ +package Common + +// StreamReader allows you to read data from a byte array in various formats +type StreamReader struct { + data []byte + position uint64 +} + +// CreateStreamReader creates an instance of the stream reader +func CreateStreamReader(source []byte) *StreamReader { + result := &StreamReader{ + data: source, + position: 0, + } + return result +} + +// GetPosition returns the current stream position +func (v *StreamReader) GetPosition() uint64 { + return v.position +} + +// GetSize returns the total size of the stream in bytes +func (v *StreamReader) GetSize() uint64 { + return uint64(len(v.data)) +} + +// GetByte returns a byte from the stream +func (v *StreamReader) GetByte() byte { + result := v.data[v.position] + v.position++ + return result +} + +// GetWord returns a uint16 word from the stream +func (v *StreamReader) GetWord() uint16 { + result := uint16(v.data[v.position]) + result += (uint16(v.data[v.position+1]) << 8) + v.position += 2 + return result +} + +// GetDword returns a uint32 dword from the stream +func (v *StreamReader) GetDword() uint32 { + result := uint32(v.data[v.position]) + result += (uint32(v.data[v.position+1]) << 8) + result += (uint32(v.data[v.position+2]) << 16) + result += (uint32(v.data[v.position+3]) << 24) + v.position += 4 + return result +} diff --git a/Common/StreamReader_test.go b/Common/StreamReader_test.go new file mode 100644 index 00000000..3a9d03ce --- /dev/null +++ b/Common/StreamReader_test.go @@ -0,0 +1,56 @@ +package Common + +import ( + "testing" +) + +func TestStreamReaderByte(t *testing.T) { + data := []byte{0x78, 0x56, 0x34, 0x12} + sr := CreateStreamReader(data) + if sr.GetPosition() != 0 { + t.Fatal("StreamReader.GetPosition() did not start at 0") + } + if ss := sr.GetSize(); ss != 4 { + t.Fatalf("StreamREader.GetSize() was expected to return %d, but returned %d instead", 4, ss) + } + for i := 0; i < len(data); i++ { + ret := sr.GetByte() + if ret != data[i] { + t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret) + } + if pos := sr.GetPosition(); pos != uint64(i+1) { + t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", i, pos) + } + } +} + +func TestStreamReaderWord(t *testing.T) { + data := []byte{0x78, 0x56, 0x34, 0x12} + sr := CreateStreamReader(data) + ret := sr.GetWord() + if ret != 0x5678 { + t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret) + } + if pos := sr.GetPosition(); pos != 2 { + t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 2, pos) + } + ret = sr.GetWord() + if ret != 0x1234 { + t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret) + } + if pos := sr.GetPosition(); pos != 4 { + t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) + } +} + +func TestStreamReaderDword(t *testing.T) { + data := []byte{0x78, 0x56, 0x34, 0x12} + sr := CreateStreamReader(data) + ret := sr.GetDword() + if ret != 0x12345678 { + t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret) + } + if pos := sr.GetPosition(); pos != 4 { + t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos) + } +} diff --git a/Common/StreamWriter.go b/Common/StreamWriter.go new file mode 100644 index 00000000..cd4b418e --- /dev/null +++ b/Common/StreamWriter.go @@ -0,0 +1,37 @@ +package Common + +// StreamWriter allows you to create a byte array by streaming in writes of various sizes +type StreamWriter struct { + data []byte +} + +// CreateStreamWriter creates a new StreamWriter instance +func CreateStreamWriter() *StreamWriter { + result := &StreamWriter{ + data: make([]byte, 0), + } + return result +} + +// PushByte writes a byte to the stream +func (v *StreamWriter) PushByte(val byte) { + v.data = append(v.data, val) +} + +// PushWord writes an uint16 word to the stream +func (v *StreamWriter) PushWord(val uint16) { + v.data = append(v.data, byte(val&0xFF)) + v.data = append(v.data, byte((val>>8)&0xFF)) +} + +// PushDword writes a uint32 dword to the stream +func (v *StreamWriter) PushDword(val uint32) { + v.data = append(v.data, byte(val&0xFF)) + v.data = append(v.data, byte((val>>8)&0xFF)) + v.data = append(v.data, byte((val>>16)&0xFF)) + v.data = append(v.data, byte((val>>24)&0xFF)) +} + +func (v *StreamWriter) GetBytes() []byte { + return v.data +} diff --git a/Common/StreamWriter_test.go b/Common/StreamWriter_test.go new file mode 100644 index 00000000..e160372d --- /dev/null +++ b/Common/StreamWriter_test.go @@ -0,0 +1,44 @@ +package Common + +import ( + "testing" +) + +func TestStreamWriterByte(t *testing.T) { + sr := CreateStreamWriter() + data := []byte{0x12, 0x34, 0x56, 0x78} + for _, d := range data { + sr.PushByte(d) + } + output := sr.GetBytes() + for i, d := range data { + if output[i] != d { + t.Fatalf("sr.PushByte() pushed %X, but wrote %X instead", d, output[i]) + } + } +} + +func TestStreamWriterWord(t *testing.T) { + sr := CreateStreamWriter() + data := []byte{0x12, 0x34, 0x56, 0x78} + sr.PushWord(0x3412) + sr.PushWord(0x7856) + output := sr.GetBytes() + for i, d := range data { + if output[i] != d { + t.Fatalf("sr.PushWord() pushed byte %X to %d, but %X was expected instead", output[i], i, d) + } + } +} + +func TestStreamWriterDword(t *testing.T) { + sr := CreateStreamWriter() + data := []byte{0x12, 0x34, 0x56, 0x78} + sr.PushDword(0x78563412) + output := sr.GetBytes() + for i, d := range data { + if output[i] != d { + t.Fatalf("sr.PushDword() pushed byte %X to %d, but %X was expected instead", output[i], i, d) + } + } +} diff --git a/README.md b/README.md index e563f5f4..3012c305 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,39 @@ To run the project, run `go run ./cmd/Client` from the root folder. You can also open the root folder in VSCode. Make sure you have the `ms-vscode.go` plugin installed. +## VS Code Extensions + +The following extensions are recommended for working with this project: + * ms-vscode.go + * defaltd.go-coverage-viewer + +For the Go extension, it is recommended you add the following to settings.json: +```json + "go.languageServerExperimentalFeatures": { + "format": true, + "autoComplete": true, + "rename": true, + "goToDefinition": true, + "hover": true, + "signatureHelp": true, + "goToTypeDefinition": true, + "goToImplementation": true, + "documentSymbols": true, + "workspaceSymbols": true, + "findReferences": true, + "diagnostics": true, + "documentLink": true + }, +``` + +You can get to it by going to settings Ctrl+,, expanding `Extensions` and selecting `Go configuration`, +then clicking on `Edit in settings.json`. Just paste that section where appropriate. + ## Configuration -The engine is configured via the `config.json` file. +The engine is configured via the `config.json` file. By default, the configuration assumes that you have installed Diablo 2 and the +expansion via the official Blizzard Diablo2 installers using the default file paths. If you are not on Windows, or have installed +the game in a different location, the base path may have to be adjusted. ## Contributing