Skip to content

Commit

Permalink
Add various branch converters
Browse files Browse the repository at this point in the history
Add support for ARM, x86/BCJ, PPC and SPARC executables.

Fixes #62
Fixes #63
  • Loading branch information
bodgit committed Apr 23, 2023
1 parent 35d7d04 commit cd74309
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Current status:
* Handles password-protected versions of both of the above (`7za a -mhc=on|off -mhe=on -ppassword test.7z ...`).
* Handles archives split into multiple volumes, (`7za a -v100m test.7z ...`).
* Validates CRC values as it parses the file.
* Supports BCJ2, Brotli, Bzip2, Copy, Deflate, Delta, LZ4, LZMA, LZMA2 and Zstandard methods.
* Supports ARM, BCJ, BCJ2, Brotli, Bzip2, Copy, Deflate, Delta, LZ4, LZMA, LZMA2, PPC, SPARC and Zstandard methods.
* Implements the `fs.FS` interface so you can treat an opened 7-zip archive like a filesystem.

More examples of 7-zip archives are needed to test all of the different combinations/algorithms possible.
55 changes: 55 additions & 0 deletions internal/bra/arm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package bra

import (
"encoding/binary"
"io"
)

const armAlignment = 4

type arm struct {
ip uint32
}

func (c *arm) Size() int { return armAlignment }

func (c *arm) Convert(b []byte, encoding bool) int {
if len(b) < c.Size() {
return 0
}

if c.ip == 0 {
c.ip += armAlignment
}

var i int

for i = 0; i < len(b) & ^(armAlignment-1); i += armAlignment {
v := binary.LittleEndian.Uint32(b[i:])

c.ip += uint32(armAlignment)

if b[i+3] == 0xeb {
v <<= 2

if encoding {
v += c.ip
} else {
v -= c.ip
}

v >>= 2
v &= 0x00ffffff
v |= 0xeb000000
}

binary.LittleEndian.PutUint32(b[i:], v)
}

return i
}

// NewARMReader returns a new ARM io.ReadCloser.
func NewARMReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
return newReader(readers, new(arm))
}
104 changes: 104 additions & 0 deletions internal/bra/bcj.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package bra

import (
"encoding/binary"
"io"
)

const bcjLookAhead = 4

type bcj struct {
ip, state uint32
}

func (c *bcj) Size() int { return bcjLookAhead + 1 }

func test86MSByte(b byte) bool {
return (b+1)&0xfe == 0
}

//nolint:cyclop,funlen,gocognit
func (c *bcj) Convert(b []byte, encoding bool) int {
if len(b) < c.Size() {
return 0
}

var (
pos int
mask = c.state & 7
)

for {
p := pos
for ; p < len(b)-bcjLookAhead; p++ {
if b[p]&0xfe == 0xe8 {
break
}
}

d := p - pos
pos = p

if p >= len(b)-bcjLookAhead {
if d > 2 {
c.state = 0
} else {
c.state = mask >> d
}

c.ip += uint32(pos)

return pos
}

if d > 2 {
mask = 0
} else {
mask >>= d
if mask != 0 && (mask > 4 || mask == 3 || test86MSByte(b[p+int(mask>>1)+1])) {
mask = (mask >> 1) | 4
pos++

continue
}
}

//nolint:nestif
if test86MSByte(b[p+4]) {
v := binary.LittleEndian.Uint32(b[p+1:])
cur := c.ip + uint32(c.Size()+pos)
pos += c.Size()

if encoding {
v += cur
} else {
v -= cur
}

if mask != 0 {
sh := mask & 6 << 2
if test86MSByte(byte(v >> sh)) {
v ^= (uint32(0x100) << sh) - 1
if encoding {
v += cur
} else {
v -= cur
}
}

mask = 0
}

binary.LittleEndian.PutUint32(b[p+1:], v)
b[p+4] = 0 - b[p+4]&1
} else {
mask = (mask >> 1) | 4
pos++
}
}
}

// NewBCJReader returns a new BCJ io.ReadCloser.
func NewBCJReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
return newReader(readers, new(bcj))
}
14 changes: 14 additions & 0 deletions internal/bra/bra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package bra

type converter interface {
Size() int
Convert([]byte, bool) int
}

func max(x, y int) int {
if x > y {
return x
}

return y
}
48 changes: 48 additions & 0 deletions internal/bra/ppc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package bra

import (
"encoding/binary"
"io"
)

const ppcAlignment = 4

type ppc struct {
ip uint32
}

func (c *ppc) Size() int { return ppcAlignment }

func (c *ppc) Convert(b []byte, encoding bool) int {
if len(b) < c.Size() {
return 0
}

var i int

for i = 0; i < len(b) & ^(ppcAlignment-1); i += ppcAlignment {
v := binary.BigEndian.Uint32(b[i:])

if b[i+0]&0xfc == 0x48 && b[i+3]&3 == 1 {
if encoding {
v += c.ip
} else {
v -= c.ip
}

v &= 0x03ffffff
v |= 0x48000000
}

c.ip += uint32(ppcAlignment)

binary.BigEndian.PutUint32(b[i:], v)
}

return i
}

// NewPPCReader returns a new PPC io.ReadCloser.
func NewPPCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
return newReader(readers, new(ppc))
}
51 changes: 51 additions & 0 deletions internal/bra/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package bra

import (
"bytes"
"errors"
"io"
)

type readCloser struct {
rc io.ReadCloser
buf bytes.Buffer
conv converter
}

func (rc *readCloser) Close() (err error) {
if rc.rc != nil {
err = rc.rc.Close()
rc.rc = nil
}

return
}

func (rc *readCloser) Read(p []byte) (int, error) {
if rc.rc == nil {
return 0, errors.New("bra: Read after Close")
}

if _, err := io.CopyN(&rc.buf, rc.rc, int64(max(len(p), rc.conv.Size())-rc.buf.Len())); err != nil {
if !errors.Is(err, io.EOF) {
return 0, err
}
}

if n := rc.conv.Convert(rc.buf.Bytes(), false); n > 0 {
return rc.buf.Read(p[:n])
}

return rc.buf.Read(p)
}

func newReader(readers []io.ReadCloser, conv converter) (io.ReadCloser, error) {
if len(readers) != 1 {
return nil, errors.New("bra: need exactly one reader")
}

return &readCloser{
rc: readers[0],
conv: conv,
}, nil
}
53 changes: 53 additions & 0 deletions internal/bra/sparc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package bra

import (
"encoding/binary"
"io"
)

const sparcAlignment = 4

type sparc struct {
ip uint32
}

func (c *sparc) Size() int { return sparcAlignment }

func (c *sparc) Convert(b []byte, encoding bool) int {
if len(b) < c.Size() {
return 0
}

var i int

for i = 0; i < len(b) & ^(sparcAlignment-1); i += sparcAlignment {
v := binary.BigEndian.Uint32(b[i:])

if (b[i+0] == 0x40 && b[i+1]&0xc0 == 0) || (b[i+0] == 0x7f && b[i+1] >= 0xc0) {
v <<= 2

if encoding {
v += c.ip
} else {
v -= c.ip
}

v &= 0x01ffffff
v -= uint32(1) << 24
v ^= 0xff000000
v >>= 2
v |= 0x40000000
}

c.ip += uint32(sparcAlignment)

binary.BigEndian.PutUint32(b[i:], v)
}

return i
}

// NewSPARCReader returns a new SPARC io.ReadCloser.
func NewSPARCReader(_ []byte, _ uint64, readers []io.ReadCloser) (io.ReadCloser, error) {
return newReader(readers, new(sparc))
}
16 changes: 16 additions & 0 deletions reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ func TestOpenReader(t *testing.T) {
name: "zstd",
file: "zstd.7z",
},
{
name: "bcj",
file: "bcj.7z",
},
{
name: "ppc",
file: "ppc.7z",
},
{
name: "arm",
file: "arm.7z",
},
{
name: "sparc",
file: "sparc.7z",
},
}

for _, table := range tables {
Expand Down
9 changes: 9 additions & 0 deletions register.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/bodgit/sevenzip/internal/aes7z"
"github.com/bodgit/sevenzip/internal/bcj2"
"github.com/bodgit/sevenzip/internal/bra"
"github.com/bodgit/sevenzip/internal/brotli"
"github.com/bodgit/sevenzip/internal/bzip2"
"github.com/bodgit/sevenzip/internal/deflate"
Expand Down Expand Up @@ -42,8 +43,16 @@ func init() {
RegisterDecompressor([]byte{0x03}, Decompressor(delta.NewReader))
// LZMA
RegisterDecompressor([]byte{0x03, 0x01, 0x01}, Decompressor(lzma.NewReader))
// BCJ
RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x03}, Decompressor(bra.NewBCJReader))
// BCJ2
RegisterDecompressor([]byte{0x03, 0x03, 0x01, 0x1b}, Decompressor(bcj2.NewReader))
// PPC
RegisterDecompressor([]byte{0x03, 0x03, 0x02, 0x05}, Decompressor(bra.NewPPCReader))
// ARM
RegisterDecompressor([]byte{0x03, 0x03, 0x05, 0x01}, Decompressor(bra.NewARMReader))
// SPARC
RegisterDecompressor([]byte{0x03, 0x03, 0x08, 0x05}, Decompressor(bra.NewSPARCReader))
// Deflate
RegisterDecompressor([]byte{0x04, 0x01, 0x08}, Decompressor(deflate.NewReader))
// Bzip2
Expand Down
Binary file added testdata/arm.7z
Binary file not shown.
Binary file added testdata/bcj.7z
Binary file not shown.
Binary file added testdata/ppc.7z
Binary file not shown.
Binary file added testdata/sparc.7z
Binary file not shown.

0 comments on commit cd74309

Please sign in to comment.