【Java】自建IOS应用(IPA)发布服务器

       简单描述一下总的过程:在某个后台上(版本发布平台)上传原始的ipa文件,解析ipa(主要是解析info.plist,从中获取软件名、版本、icons等;解析embedded.mobileprovision,获取证书过期时间),生成一个新的plist文件,最终将ipa、新的plist、图标上传至发布服务器。这个plist文件里面会指向这个ipa的地址,最终在Safari上访问 itms-services://?action=download-manifest&url=plist文件 就可以下载IOS应用了。

下面先来看看最重要的plist文件.




    
        items
        
            
                assets
                
                    
                        kind
                        software-package
                        url
                        https://uc-download.xxx.com/api/v1/file/download/5194c4cfcb3d4c128c51b9e3138ca8e8
                    
                    
                        kind
                        full-size-image
                        needs-shine
                        
                        url
                        https://uc-download.xxx.com/api/v1/file/download/d11184a8a9184c00836f86e14ed11d10
                    
                    
                        kind
                        display-image
                        needs-shine
                        
                        url
                        https://uc-download.xxx.com/api/v1/file/download/d11184a8a9184c00836f86e14ed11d10
                    
                
                metadata
                
                    bundle-identifier
                    enterprise.xxx.Odin-UC
                    bundle-version
                    2.0.0.66
                    kind
                    software
                    title
                    UME
                
            
        
    

一、服务器

首先,我们需要一台服务器,并且有SSL证书。这是因为最终在Safari上访问plist文件需要。

二、解析IPA

FileService这里主要用到就是在本地构造临时文件,ClientVersionRecord是数据库存储的版本实体对象,比较简单,不再赘述。

controller入口可以见:https://blog.csdn.net/Mr_EvanChen/article/details/106937688

/**
 * 解析ios安装包,后缀名为ipa
 */
@Service
public class IOSPacketParser extends InstallPacketParser {

    private static final Logger logger = LoggerFactory.getLogger(IOSPacketParser.class);

    @Autowired
    private FileService fileService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private IosIntranetService iosIntranetService;

    @Override
    public ClientType getClientType() {
        return ClientType.IOS;
    }

    @Override
    public ClientVersionRecord parse(File ipaFile, ClientVersionRecord version) throws Exception {

        String intranetIpaUrl = version.getIpaDownloadUrl();
        String intranetUrl = intranetIpaUrl.substring(0, intranetIpaUrl.lastIndexOf("/") + 1);
        IPA ipa = iosIntranetService.transFile2IPA(ipaFile);
        String ipaFileName = ipaFile.getName();
        ipaFileName = ipaFileName.substring(0, ipaFileName.lastIndexOf("."));

        //生成icon
        String iconUrl = buildIcon(ipaFile, ipa, intranetUrl + ipaFileName);

        //生成plist

        File plistFile = iosIntranetService.createPlistFile(ipa, intranetIpaUrl, iconUrl, ipaFileName);
        String plistUrl = iosIntranetService.uploadPlistAsFile(plistFile, intranetUrl + plistFile.getName());

        version.setMd5(FileUtil.md5(ipaFile))
                .setFileSize(ipaFile.length())
                .setClientVersion(ipa.getCFBundleVersion())
                .setIconUrl(iconUrl)
                .setIntranetDownloadUrl("itms-services://?action=download-manifest&url=" + plistUrl);

        logger.info("parse [ipa] file, version: {}", JSONUtil.serialize(version));
        return version;
    }

    //----------------------------------private-----------------------------------------

    private String buildIcon(File ipaFile, IPA ipa, String intranetIconUrl) {
        String iconName = getMaxIcon(ipa);
        File oriIcon = null;
        File tmpDecodedIcon = null;

        try {
            oriIcon = readIcons(ipaFile, iconName);
            tmpDecodedIcon = new File(fileService.getTempDir() + "decoded-" + oriIcon.getName());
            new IPngConverter(oriIcon, tmpDecodedIcon).convert();

            String oriIconName = oriIcon.getName();
            intranetIconUrl = intranetIconUrl + StringPool.DOT + oriIconName.substring(oriIconName.lastIndexOf(StringPool.DOT) + 1);
            restTemplate.put(intranetIconUrl, new FileSystemResource(tmpDecodedIcon));

        } catch (Exception e) {
            logger.info("上传icon至内网失败,结果:{}", intranetIconUrl);
        } finally {
            if (oriIcon != null) {
                logger.info("删除临时文件,结果:{}", oriIcon.delete());
                logger.info("删除临时文件,结果:{}", oriIcon.getParentFile().delete());
            }

            if (tmpDecodedIcon != null) {
                logger.info("删除临时文件,结果:{}",tmpDecodedIcon.delete());
                logger.info("删除临时文件,结果:{}",tmpDecodedIcon.getParentFile().delete());
            }
        }

        return intranetIconUrl;
    }

    private File readIcons(File ipaFile, String icon) {

        if (StringUtil.isNotBlank(icon)) {
            icon = "AppIcon";
        }

        long maxSize = Long.MIN_VALUE;
        ZipEntry maxEntry = null;

        try (ZipFile zipFile = new ZipFile(ipaFile)) {

            Enumeration e = zipFile.entries();

            while (e.hasMoreElements()) {

                ZipEntry ze = (ZipEntry) e.nextElement();

                if (ze.isDirectory()) {
                    continue;
                }

                String name = ze.getName();
                long size = ze.getSize();

                if (name != null && name.contains(icon) && size > maxSize) {
                    maxSize = size;
                    maxEntry = ze;
                }
            }

            if (maxEntry != null) {
                return fileService.buildTmpFile(zipFile.getInputStream(maxEntry), FileNameUtil.getName(maxEntry.getName()));
            }

        } catch (IOException e) {
            throw new InternalServerException("parse error");
        }

        throw new ResourceNotFoundException("icon not found");
    }

    private static final Pattern pattern = Pattern.compile("[+-]?\\d+(\\.\\d+)?x[+-]?\\d+(\\.\\d+)?");

    /**
     * 获取最大的图标,根据图标名称解析
     *
     * @param ipa ipa信息
     * @return 最大图标名称
     */
    private static String getMaxIcon(IPA ipa) {
        if (isEmpty(ipa.getIcons())) {
            return null;
        }

        double maxSize = Double.MIN_VALUE;
        String iconName = null;

        for (String icon : ipa.getIcons()) {

            Matcher matcher = pattern.matcher(icon);

            if (matcher.find()) {
                try {
                    String[] sizes = matcher.group(0).split("x");
                    Double d = Double.valueOf(sizes[0]);
                    if (d > maxSize) {
                        maxSize = d;
                        iconName = icon;
                    }
                } catch (Exception e) {
                    logger.error("parse size error", e);
                }
            }

        }

        return iconName;
    }

    public static void main(String[] args) {
        logger.info(getMaxIcon(new IPA().setIcons(Arrays.asList("AppIcon20x20", "AppIcon29x29", "AppIcon40x40", "AppIcon60x60"))));
    }

}

IosIntranetService接口及其实现类如下。

public interface IosIntranetService {

    String handleIPAFile(File ipaFile, ClientVersionProtocol.Download.Input input, String uploadUrl) throws Exception;

    /**
     * ipa文件解析成对象
     * @param ipaFile
     * @return
     */
    IPA transFile2IPA(File ipaFile) throws Exception;

    /**
     * 从ipa对象和两个地址创建出Plist对象
     * @param ipa
     * @param ipaUrl
     * @param iconUrl
     * @return
     */
    File createPlistFile(IPA ipa, String ipaUrl, String iconUrl,String plistName);

    /**
     * 上传Plist文件,返回uri
     * @param plistFile
     * @param uploadUrl
     * @return
     */
    String uploadPlistAsFile(File plistFile,String uploadUrl);
}
@Service
public class IosIntranetServiceImp implements IosIntranetService {
    Logger logger = LoggerFactory.getLogger(IosIntranetService.class);

    private Configuration configuration;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${com.xxx.uc.manager.tmp.dir}")
    private String tmpDir;

    @PostConstruct
    private void init() {
        configuration = new Configuration(Configuration.VERSION_2_3_23);
        configuration.setTemplateLoader(new ClassTemplateLoader(this.getClass(), "/template/"));
    }

    @Override
    public String handleIPAFile(File ipaFile, ClientVersionProtocol.Download.Input input, String uploadUrl) throws Exception {

        String localIconUrl = uploadImageFile(input.getIconUrl(), uploadUrl);

        IPA ipa = transFile2IPA(ipaFile);

        String ipaFileName = ipaFile.getName();
        ipaFileName = ipaFileName.substring(0, ipaFileName.lastIndexOf("."));

        File plistFile = createPlistFile(ipa, uploadUrl + ipaFile.getName(), localIconUrl, ipaFileName);
        return uploadPlistAsFile(plistFile, uploadUrl + plistFile.getName());

    }

    @Override
    public IPA transFile2IPA(File ipaFile) throws Exception {
        return IPAUtil.readIPA(ipaFile);
    }

    @Override
    public File createPlistFile(IPA ipa, String ipaUrl, String iconUrl, String plistName) {

        Plist plist = new Plist(ipa)
                .setFullSizeImageUrl(iconUrl)
                .setDisplayImageUrl(iconUrl)
                .setIpaUrl(ipaUrl);

        File plistFile = new File(tmpDir + File.separator + plistName + ".plist");

        try {
            getTemplate().process(plist, new FileWriter(plistFile));
        } catch (TemplateException e) {
            logger.error("freemarker生成pList文件出现异常:{}", e);
        } catch (IOException e) {
            logger.error("生成pList临时文件出现异常:{}", e);
        }
        return plistFile;
    }

    public String uploadPlistAsFile(File plistFile, String uploadUrl) {
        try {
            restTemplate.put(uploadUrl, new FileSystemResource(plistFile));
        } catch (Exception e) {
            logger.error("上传plist{}至内网服务器{}失败!{}", plistFile.getName(), uploadUrl, Throwables.getStackTraceAsString(e));
            // 上传失败
            return null;
        } finally {
            FileUtils.deleteQuietly(plistFile);
        }
        return uploadUrl;
    }

    /**
     * 这个上传url没添加文件名
     *
     * @param originalUrl
     * @param uploadUrl
     * @return
     */

    public String uploadImageFile(String originalUrl, String uploadUrl) {

        String filename = originalUrl.substring(originalUrl.lastIndexOf("/"));
        File rcvFile = new File(tmpDir + File.separator + filename);
        try {
            //创建目录
            // 从公网下载至本地
            try {
                restTemplate.execute(originalUrl, HttpMethod.GET, null, response -> {
                    if (response.getStatusCode() == HttpStatus.OK) {
                        FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));
                        return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);
                    } else {
                        return null;
                    }
                });
            } catch (Exception e) {
                logger.error("从公网下载图标文件{}失败!{}", rcvFile.getName(), Throwables.getStackTraceAsString(e));
            }

            restTemplate.put(uploadUrl + filename, new FileSystemResource(rcvFile));
        } catch (Exception e) {
            logger.error("上传图标文件{}至内网服务器{}失败!{}", filename, uploadUrl, Throwables.getStackTraceAsString(e));
            // 上传失败
            return null;
        } finally {
            FileUtils.deleteQuietly(rcvFile);
        }
        return uploadUrl + filename;
    }

    protected Template getTemplate() throws IOException {
        return configuration.getTemplate("plist.xml");
    }
}

Plist对象如下。

public class Plist {

    private String ipaUrl;

    private String fullSizeImageUrl;

    private String displayImageUrl;

    private String bundleIdentifier;

    private String bundleVersion;

    private String title;

    public Plist() {
    }

    public Plist(IPA ipa) {
        this.bundleIdentifier = ipa.getCFBundleIdentifier();
        this.bundleVersion = ipa.getCFBundleVersion();
        this.title = ipa.getCFBundleDisplayName();
    }
  
}

Plist模板可放在resource下,模板如下。

【Java】自建IOS应用(IPA)发布服务器_第1张图片

itemsassetskindsoftware-packageurl${ipaUrl}kindfull-size-imageneeds-shineurl${fullSizeImageUrl}kinddisplay-imageneeds-shineurl${displayImageUrl}metadatabundle-identifier${bundleIdentifier}bundle-version${bundleVersion}kindsoftwaretitle${title}

IPngConverter、PNGTrunk、PNGIHDRTrunk如下。

import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;

import java.io.*;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.zip.CRC32;

/**
 * @author Rex
 */
public class IPngConverter {

    private static final Logger log = Logger.getLogger(IPngConverter.class.getName());

    private final File source;
    private final File target;
    private ArrayList trunks = null;

    public static void main(String[] args) throws Exception {
        log.info("===");
        File file = new File("E:\\needFixPng");

        for (File file1 : file.listFiles()) {
            try {
                new IPngConverter(file1, new File("E:\\fixed\\" + file1.getName()))
                        .convert();
            } catch (Exception e) {
                log.info(e +"");
            }
        }

    }


    public IPngConverter(File source, File target) {
        if (source == null) throw new NullPointerException("'source' cannot be null");
        if (target == null) throw new NullPointerException("'target' cannot be null");
        this.source = source;
        this.target = target;
    }

    private File getTargetFile(File convertedFile) throws IOException {
        if (source.isFile()) {
            if (target.isDirectory()) {
                return new File(target, source.getName());
            } else {
                return target;
            }

        } else { // source is a directory
            if (target.isFile()) { // single existing target
                return target;

            } else { // otherwise reconstruct a similar directory structure
                if (!target.isDirectory() && !target.mkdirs()) {
                    throw new IOException("failed to create folder " + target.getAbsolutePath());
                }

                Path relativeConvertedPath = source.toPath().relativize(convertedFile.toPath());
                File targetFile = new File(target, relativeConvertedPath.toString());
                File targetFileDir = targetFile.getParentFile();
                if (targetFileDir != null && !targetFileDir.exists() && !targetFileDir.mkdirs()) {
                    throw new IOException("unable to create folder " + targetFileDir.getAbsolutePath());
                }

                return targetFile;
            }
        }
    }

    public void convert() throws IOException {
        convert(source);
    }

    private boolean isPngFileName(File file) {
        return file.getName().toLowerCase().endsWith(".png");
    }

    private PNGTrunk getTrunk(String szName) {
        if (trunks == null) {
            return null;
        }
        PNGTrunk trunk;
        for (int n = 0; n < trunks.size(); n++) {
            trunk = trunks.get(n);
            if (trunk.getName().equalsIgnoreCase(szName)) {
                return trunk;
            }
        }
        return null;
    }

    private void convertPngFile(File pngFile, File targetFile) throws IOException {
        readTrunks(pngFile);

        if (getTrunk("CgBI") != null) {
            // Convert data

            PNGIHDRTrunk ihdrTrunk = (PNGIHDRTrunk) getTrunk("IHDR");
            log.fine("Width:" + ihdrTrunk.m_nWidth + "  Height:" + ihdrTrunk.m_nHeight);

            int nMaxInflateBuffer = 4 * (ihdrTrunk.m_nWidth + 1) * ihdrTrunk.m_nHeight;
            byte[] outputBuffer = new byte[nMaxInflateBuffer];

            convertDataTrunk(ihdrTrunk, outputBuffer, nMaxInflateBuffer);

            writePng(targetFile);

        } else {
            // Likely a standard PNG: just copy
            byte[] buffer = new byte[1024];
            int bytesRead;
            InputStream inputStream = new FileInputStream(pngFile);
            try {
                OutputStream outputStream = new FileOutputStream(targetFile);
                try {
                    while ((bytesRead = inputStream.read(buffer)) >= 0) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                    outputStream.flush();

                } finally {
                    outputStream.close();
                }
            } finally {
                inputStream.close();
            }
        }
    }

    private long inflate(byte[] conversionBuffer, int nMaxInflateBuffer) throws GZIPException {
        Inflater inflater = new Inflater(-15);

        for (PNGTrunk dataTrunk : trunks) {
            if (!"IDAT".equalsIgnoreCase(dataTrunk.getName())) continue;
            inflater.setInput(dataTrunk.getData(), true);
        }

        inflater.setOutput(conversionBuffer);

        int nResult;
        try {
            nResult = inflater.inflate(JZlib.Z_NO_FLUSH);
            checkResultStatus(nResult);
        } finally {
            inflater.inflateEnd();
        }

        if (inflater.getTotalOut() > nMaxInflateBuffer) {
            log.fine("PNGCONV_ERR_INFLATED_OVER");
        }

        return inflater.getTotalOut();
    }

    private Deflater deflate(byte[] buffer, int length, int nMaxInflateBuffer) throws GZIPException {
        Deflater deflater = new Deflater();
        deflater.setInput(buffer, 0, length, false);

        int nMaxDeflateBuffer = nMaxInflateBuffer + 1024;
        byte[] deBuffer = new byte[nMaxDeflateBuffer];
        deflater.setOutput(deBuffer);

        deflater.deflateInit(JZlib.Z_BEST_COMPRESSION);
        int nResult = deflater.deflate(JZlib.Z_FINISH);
        checkResultStatus(nResult);

        if (deflater.getTotalOut() > nMaxDeflateBuffer) {
            throw new GZIPException("deflater output buffer was too small");
        }

        return deflater;
    }

    private void checkResultStatus(int nResult) throws GZIPException {
        switch (nResult) {
            case JZlib.Z_OK:
            case JZlib.Z_STREAM_END:
                break;

            case JZlib.Z_NEED_DICT:
                throw new GZIPException("Z_NEED_DICT - " + nResult);
            case JZlib.Z_DATA_ERROR:
                throw new GZIPException("Z_DATA_ERROR - " + nResult);
            case JZlib.Z_MEM_ERROR:
                throw new GZIPException("Z_MEM_ERROR - " + nResult);
            case JZlib.Z_STREAM_ERROR:
                throw new GZIPException("Z_STREAM_ERROR - " + nResult);
            case JZlib.Z_BUF_ERROR:
                throw new GZIPException("Z_BUF_ERROR - " + nResult);
            default:
                throw new GZIPException("inflater error: " + nResult);
        }
    }

    private boolean convertDataTrunk(
            PNGIHDRTrunk ihdrTrunk, byte[] conversionBuffer, int nMaxInflateBuffer)
            throws IOException {
        log.fine("converting colors");

        long inflatedSize = inflate(conversionBuffer, nMaxInflateBuffer);

        // Switch the color
        int nIndex = 0;
        byte nTemp;
        for (int y = 0; y < ihdrTrunk.m_nHeight; y++) {
            nIndex++;
            for (int x = 0; x < ihdrTrunk.m_nWidth; x++) {
                nTemp = conversionBuffer[nIndex];
                conversionBuffer[nIndex] = conversionBuffer[nIndex + 2];
                conversionBuffer[nIndex + 2] = nTemp;
                nIndex += 4;
            }
        }

        Deflater deflater = deflate(conversionBuffer, (int) inflatedSize, nMaxInflateBuffer);

        // Put the result in the first IDAT chunk (the only one to be written out)
        PNGTrunk firstDataTrunk = getTrunk("IDAT");

        CRC32 crc32 = new CRC32();
        crc32.update(firstDataTrunk.getName().getBytes());
        crc32.update(deflater.getNextOut(), 0, (int) deflater.getTotalOut());
        long lCRCValue = crc32.getValue();

        firstDataTrunk.m_nData = deflater.getNextOut();
        firstDataTrunk.m_nCRC[0] = (byte) ((lCRCValue & 0xFF000000) >> 24);
        firstDataTrunk.m_nCRC[1] = (byte) ((lCRCValue & 0xFF0000) >> 16);
        firstDataTrunk.m_nCRC[2] = (byte) ((lCRCValue & 0xFF00) >> 8);
        firstDataTrunk.m_nCRC[3] = (byte) (lCRCValue & 0xFF);
        firstDataTrunk.m_nSize = (int) deflater.getTotalOut();

        return false;
    }

    private void writePng(File newFileName) throws IOException {
        FileOutputStream outStream = new FileOutputStream(newFileName);
        try {
            byte[] pngHeader = {-119, 80, 78, 71, 13, 10, 26, 10};
            outStream.write(pngHeader);
            boolean dataWritten = false;
            for (PNGTrunk trunk : trunks) {
                // Skip Apple specific and misplaced CgBI chunk
                if (trunk.getName().equalsIgnoreCase("CgBI")) {
                    continue;
                }

                // Only write the first IDAT chunk as they have all been put together now
                if ("IDAT".equalsIgnoreCase(trunk.getName())) {
                    if (dataWritten) {
                        continue;
                    } else {
                        dataWritten = true;
                    }
                }

                trunk.writeToStream(outStream);
            }
            outStream.flush();

        } finally {
            outStream.close();
        }
    }

    private void readTrunks(File pngFile) throws IOException {
        DataInputStream input = new DataInputStream(new FileInputStream(pngFile));
        try {
            byte[] nPNGHeader = new byte[8];
            input.readFully(nPNGHeader);

            boolean bWithCgBI = false;

            trunks = new ArrayList();
            if ((nPNGHeader[0] == -119) && (nPNGHeader[1] == 0x50) && (nPNGHeader[2] == 0x4e) && (nPNGHeader[3] == 0x47)
                    && (nPNGHeader[4] == 0x0d) && (nPNGHeader[5] == 0x0a) && (nPNGHeader[6] == 0x1a) && (nPNGHeader[7] == 0x0a)) {

                PNGTrunk trunk;
                do {
                    trunk = PNGTrunk.generateTrunk(input);
                    trunks.add(trunk);

                    if (trunk.getName().equalsIgnoreCase("CgBI")) {
                        bWithCgBI = true;
                    }
                }
                while (!trunk.getName().equalsIgnoreCase("IEND"));
            }
        } finally {
            input.close();
        }
    }

    private void convertDirectory(File dir) throws IOException {
        for (File file : dir.listFiles()) {
            convert(file);
        }
    }

    private void convert(File sourceFile) throws IOException {
        if (sourceFile.isDirectory()) {
            convertDirectory(sourceFile);
        } else if (isPngFileName(sourceFile)) {
            File targetFile = getTargetFile(sourceFile);
            log.fine("converting " + sourceFile.getPath() + " --> " + targetFile.getPath());
            convertPngFile(sourceFile, targetFile);
        }
    }

}
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Rex
 */
public class PNGTrunk {

  protected int m_nSize;
  protected String m_szName;
  protected byte[] m_nData;
  protected byte[] m_nCRC;


  public static PNGTrunk generateTrunk(DataInputStream input) throws IOException {
    int nSize = readPngInt(input);

    byte[] nData = new byte[4];
    input.readFully(nData);
    String szName = new String(nData, "ASCII");

    byte[] nDataBuffer = new byte[nSize];
    input.readFully(nDataBuffer);

    byte[] nCRC = new byte[4];
    input.readFully(nCRC);

    if (szName.equalsIgnoreCase("IHDR")) {
      return new PNGIHDRTrunk(nSize, szName, nDataBuffer, nCRC);
    }

    return new PNGTrunk(nSize, szName, nDataBuffer, nCRC);
  }

  protected PNGTrunk(int nSize, String szName, byte[] nCRC) {
    m_nSize = nSize;
    m_szName = szName;
    m_nCRC = nCRC;
  }

  protected PNGTrunk(int nSize, String szName, byte[] nData, byte[] nCRC) {
    this(nSize, szName, nCRC);
    m_nData = nData;
  }

  public int getSize() {
    return m_nSize;
  }

  public String getName() {
    return m_szName;
  }

  public byte[] getData() {
    return m_nData;
  }

  public byte[] getCRC() {
    return m_nCRC;
  }

  public void writeToStream(FileOutputStream outStream) throws IOException {
    byte nSize[] = new byte[4];
    nSize[0] = (byte) ((m_nSize & 0xFF000000) >> 24);
    nSize[1] = (byte) ((m_nSize & 0xFF0000) >> 16);
    nSize[2] = (byte) ((m_nSize & 0xFF00) >> 8);
    nSize[3] = (byte) (m_nSize & 0xFF);

    outStream.write(nSize);
    outStream.write(m_szName.getBytes("ASCII"));
    outStream.write(m_nData, 0, m_nSize);
    outStream.write(m_nCRC);
  }

  public static void writeInt(byte[] nDes, int nPos, int nVal) {
    nDes[nPos] = (byte) ((nVal & 0xff000000) >> 24);
    nDes[nPos + 1] = (byte) ((nVal & 0xff0000) >> 16);
    nDes[nPos + 2] = (byte) ((nVal & 0xff00) >> 8);
    nDes[nPos + 3] = (byte) (nVal & 0xff);
  }

  public static int readPngInt(DataInputStream input) throws IOException {
    final byte[] buffer = new byte[4];
    input.readFully(buffer);
    return readInt(buffer, 0);
  }

  public static int readInt(byte[] nDest, int nPos) { //读一个int
    return ((nDest[nPos++] & 0xFF) << 24)
      | ((nDest[nPos++] & 0xFF) << 16)
      | ((nDest[nPos++] & 0xFF) << 8)
      | (nDest[nPos] & 0xFF);
  }

  public static void writeCRC(byte[] nData, int nPos) {
    int chunklen = readInt(nData, nPos);
    int sum = CRCChecksum(nData, nPos + 4, 4 + chunklen) ^ 0xffffffff;
    writeInt(nData, nPos + 8 + chunklen, sum);
  }

  public static int[] crc_table = null;

  public static int CRCChecksum(byte[] nBuffer, int nOffset, int nLength) {
    int c = 0xffffffff;
    int n;
    if (crc_table == null) {
      int mkc;
      int mkn, mkk;
      crc_table = new int[256];
      for (mkn = 0; mkn < 256; mkn++) {
        mkc = mkn;
        for (mkk = 0; mkk < 8; mkk++) {
          if ((mkc & 1) == 1) {
            mkc = 0xedb88320 ^ (mkc >>> 1);
          } else {
            mkc = mkc >>> 1;
          }
        }
        crc_table[mkn] = mkc;
      }
    }
    for (n = nOffset; n < nLength + nOffset; n++) {
      c = crc_table[(c ^ nBuffer[n]) & 0xff] ^ (c >>> 8);
    }
    return c;
  }
}
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * @author Rex
 */
public class PNGIHDRTrunk extends PNGTrunk {
  public int m_nWidth;
  public int m_nHeight;

  public PNGIHDRTrunk(int nSize, String szName, byte[] nData, byte[] nCRC) {
    super(nSize, szName, nData, nCRC);

    m_nWidth = readInt(nData, 0);
    m_nHeight = readInt(nData, 4);
  }
}

pom依赖。

        
            org.freemarker
            freemarker
            2.3.23
        

        
        
            com.jcraft
            jzlib
            1.1.3
        

IPAUtil、IPA实体,见:https://blog.csdn.net/Mr_EvanChen/article/details/100565769

 

你可能感兴趣的:(Java)