You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
5.7 KiB
225 lines
5.7 KiB
package tools |
|
|
|
import ( |
|
"fmt" |
|
"go-common/library/log" |
|
"os" |
|
"path/filepath" |
|
"time" |
|
) |
|
|
|
type zipSections struct { |
|
beforeSigningBlock []byte |
|
signingBlock []byte |
|
signingBlockOffset int64 |
|
centraDir []byte |
|
centralDirOffset int64 |
|
eocd []byte |
|
eocdOffset int64 |
|
} |
|
type transform func(*zipSections) (*zipSections, error) |
|
|
|
func (z *zipSections) writeTo(output string, transform transform) (err error) { |
|
f, err := os.Create(output) |
|
if err != nil { |
|
return |
|
} |
|
|
|
defer f.Close() |
|
|
|
newZip, err := transform(z) |
|
if err != nil { |
|
return |
|
} |
|
|
|
for _, s := range [][]byte{ |
|
newZip.beforeSigningBlock, |
|
newZip.signingBlock, |
|
newZip.centraDir, |
|
newZip.eocd} { |
|
_, err := f.Write(s) |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return |
|
} |
|
|
|
var _debug bool |
|
|
|
// GenerateChannelApk generate a specified channel apk |
|
func GenerateChannelApk(out string, channel string, extras map[string]string, input string, force bool, debug bool) (output string, err error) { |
|
_debug = debug |
|
if len(input) == 0 { |
|
err = fmt.Errorf("Error: no input file specified") |
|
log.Error("no input file specified, error(%v)", err) |
|
return |
|
} |
|
|
|
if _, err = os.Stat(input); os.IsNotExist(err) { |
|
return |
|
} |
|
|
|
if len(out) == 0 { |
|
out = filepath.Dir(input) |
|
} else { |
|
var fi os.FileInfo |
|
|
|
err = os.MkdirAll(out, 0755) |
|
if err != nil { |
|
log.Error("os.MkdirAll(%s) error(%v)", out, err) |
|
return |
|
} |
|
|
|
fi, err = os.Stat(out) |
|
println("error %v", err) |
|
if os.IsNotExist(err) || !fi.IsDir() { |
|
err = fmt.Errorf("Error: output %s is neither exist nor a dir", out) |
|
log.Error("output %s is neither exist nor a dir, error(%v)", out, err) |
|
return |
|
} |
|
} |
|
if channel == "" { |
|
err = fmt.Errorf("Error: no channel specified") |
|
log.Error("no channel specified, error(%v)", err) |
|
return |
|
} |
|
|
|
//TODO: add new option for generating new channel from channelled apk |
|
if c, _ := readChannelInfo(input); len(c.Channel) != 0 { |
|
err = fmt.Errorf("Error: file %s is registered a channel block %s", filepath.Base(input), c.String()) |
|
log.Error("file %s is registered a channel block %s, error(%v)", filepath.Base(input), c.String(), err) |
|
return |
|
} |
|
var start time.Time |
|
if _debug { |
|
start = time.Now() |
|
} |
|
|
|
z, err := newZipSections(input) |
|
if err != nil { |
|
err = fmt.Errorf("Error occurred on parsing apk %s, %s", input, err) |
|
log.Error("Error occurred on parsing apk %s, error(%v)", input, err) |
|
return |
|
} |
|
name, ext := fileNameAndExt(input) |
|
output = filepath.Join(out, name+"-"+channel+ext) |
|
c := ChannelInfo{Channel: channel, Extras: extras} |
|
err = gen(c, z, output, force) |
|
if err != nil { |
|
err = fmt.Errorf("Error occurred on generating channel %s, %s", channel, err) |
|
log.Error("Error occurred on generating channel %s, error(%v)", input, err) |
|
return |
|
} |
|
if _debug { |
|
println("Consume", time.Since(start).String()) |
|
} else { |
|
println("Done!") |
|
} |
|
return |
|
} |
|
|
|
func newZipSections(input string) (z zipSections, err error) { |
|
in, err := os.Open(input) |
|
if err != nil { |
|
return |
|
} |
|
defer in.Close() |
|
|
|
// read eocd |
|
eocd, eocdOffset, err := findEndOfCentralDirectoryRecord(in) |
|
if err != nil { |
|
return |
|
} |
|
centralDirOffset := getEocdCentralDirectoryOffset(eocd) |
|
centralDirSize := getEocdCentralDirectorySize(eocd) |
|
z.eocd = eocd |
|
z.eocdOffset = eocdOffset |
|
z.centralDirOffset = int64(centralDirOffset) |
|
|
|
// read signing block |
|
signingBlock, signingBlockOffset, err := findApkSigningBlock(in, centralDirOffset) |
|
if err != nil { |
|
return |
|
} |
|
z.signingBlock = signingBlock |
|
z.signingBlockOffset = signingBlockOffset |
|
// read bytes before signing block |
|
//TODO: waste too large memory |
|
if signingBlockOffset >= 64*1024*1024 { |
|
fmt.Print("Warning: maybe waste large memory on processing this apk! ") |
|
fmt.Println("Before APK Signing Block bytes size is", signingBlockOffset/1024/1024, "MB") |
|
} |
|
beforeSigningBlock := make([]byte, signingBlockOffset) |
|
n, err := in.ReadAt(beforeSigningBlock, 0) |
|
if err != nil { |
|
return |
|
} |
|
if int64(n) != signingBlockOffset { |
|
return z, fmt.Errorf("Read bytes count mismatched! Expect %d, but %d", signingBlockOffset, n) |
|
} |
|
z.beforeSigningBlock = beforeSigningBlock |
|
|
|
centralDir := make([]byte, centralDirSize) |
|
n, err = in.ReadAt(centralDir, int64(centralDirOffset)) |
|
if uint32(n) != centralDirSize { |
|
return z, fmt.Errorf("Read bytes count mismatched! Expect %d, but %d", centralDirSize, n) |
|
} |
|
z.centraDir = centralDir |
|
if _debug { |
|
fmt.Printf("signingBlockOffset=%d, signingBlockLenth=%d\n"+ |
|
"centralDirOffset=%d, centralDirSize=%d\n"+ |
|
"eocdOffset=%d, eocdLenthe=%d\n", |
|
signingBlockOffset, |
|
len(signingBlock), |
|
centralDirOffset, |
|
centralDirSize, |
|
eocdOffset, |
|
len(eocd)) |
|
} |
|
return |
|
} |
|
|
|
func gen(info ChannelInfo, sections zipSections, output string, force bool) (err error) { |
|
|
|
fi, err := os.Stat(output) |
|
if err != nil && !os.IsNotExist(err) { |
|
return err |
|
} |
|
if fi != nil { |
|
if !force { |
|
return fmt.Errorf("file already exists %s", output) |
|
} |
|
println("Force generating channel", info.Channel) |
|
} |
|
|
|
var s time.Time |
|
if _debug { |
|
s = time.Now() |
|
} |
|
err = sections.writeTo(output, newTransform(info)) |
|
if _debug { |
|
fmt.Printf(" write %s consume %s", output, time.Since(s).String()) |
|
fmt.Println() |
|
} |
|
return |
|
} |
|
|
|
func newTransform(info ChannelInfo) transform { |
|
return func(zip *zipSections) (*zipSections, error) { |
|
|
|
newBlock, diffSize, err := makeSigningBlockWithChannelInfo(info, zip.signingBlock) |
|
if err != nil { |
|
return nil, err |
|
} |
|
newzip := new(zipSections) |
|
newzip.beforeSigningBlock = zip.beforeSigningBlock |
|
newzip.signingBlock = newBlock |
|
newzip.signingBlockOffset = zip.signingBlockOffset |
|
newzip.centraDir = zip.centraDir |
|
newzip.centralDirOffset = zip.centralDirOffset |
|
newzip.eocdOffset = zip.eocdOffset |
|
newzip.eocd = makeEocd(zip.eocd, uint32(int64(diffSize)+zip.centralDirOffset)) |
|
return newzip, nil |
|
} |
|
}
|
|
|