舍得英语魔法学苑

 找回密码
 注册
查看: 10521|回复: 20

[UX] 制作和解压course.smpak文件的相关代码

[复制链接]
  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

    舍得 发表于 2011-9-4 14:47:47 | 显示全部楼层 |阅读模式
    去年m4gic兄用UE之类的工具,对course.smpak文件进行了解析,分析的很到位,有兴趣的童鞋可以查看此贴:
    http://emagic.org.cn/thread-10398-1-1.html

    先来讲讲什么是course.smpak文件以及讨论它的意义:
    简单地讲,scourse.smpak是UX课程的主体文件,它的最大功能在于能把一个课程里的各项资源,如文本、音频、图片、模板等文件放到一个包里。
    大家通常使用的course.smpak是通用的,它主要包括了程序自带的模板,没有将上面说到的其它资源放到里边.如果我们能够制作出单一的course.smpak的课程,就像官方推出的Extreme English等课程一样,那么,我们就能够让自己的iphone版课程调用声音/图片等媒体,这是研究course.smpak最大的意义所在.

    舍得通过对SuperMemo主程序和波兰人Michael的插件的分析,从主程序中提取出一些与SMPAK文件的读取和写入相关的一些资料,供大家参考:
    先来看DisposeArchive这段代码:

    1. protected void DisposeArchive(bool disposing)//配置压缩包
    2. {
    3.     if (this.stream != null)
    4.     {
    5.         this.Flush();
    6.         this.Close();
    7.         this.bytes = this.enc.GetBytes("EntrChnk");//数据簇
    8.         this.binWriter.Write(this.bytes, 0, 8);
    9.         int position = (int) this.stream.Position;//位置
    10.         this.binWriter.Write(this.entries.Count);//词条数,其实是文件总数
    11.         this.entries.Sort();//排序
    12.         StringBuilder nameBuf = new StringBuilder();//构建名称
    13.         foreach (DataFile.Entry entry in this.entries)//循环
    14.         {
    15.             entry.write(this.binWriter, nameBuf);//名称区
    16.         }
    17.         this.bytes = this.enc.GetBytes("NameChnk");
    18.         this.binWriter.Write(this.bytes, 0, 8);//写入
    19.         int namesOffset = (int) this.stream.Position;//名称偏移值
    20.         this.binWriter.Write(nameBuf.Length);//写名称长度
    21.         this.binWriter.Write(nameBuf.ToString());//写入文件名(含相对路径)
    22.         this.stream.Position = 0;
    23.         this.writeHeader(position, namesOffset);//写头部的名称偏移值
    24.         this.stream.Close();
    25.         this.stream.Dispose();
    26.         this.stream = null;
    27.     }
    28. }
    复制代码

    再来看writeHeader,这段代码的主要功能应当是写smpak的头部:
    1. private void writeHeader(int entriesOffset, int namesOffset)//写头部
    2. {
    3.     this.bytes = this.enc.GetBytes("-SMArch-");//转为bytes类型,这个标记在smpak文件中出现
    4.     this.binWriter.Write(this.bytes, 0, 8);#写入标记
    5.     this.binWriter.Write((short) 0x101);#写版本号
    6.     this.binWriter.Write((short) this.flags);#写锁定标记
    7.     this.binWriter.Write(entriesOffset);#写文件列表区偏移值
    8.     this.binWriter.Write(namesOffset);#写文件名列表区偏移值
    9. }
    复制代码
    下面这一段代码,是处理课程页面的:
    1. public void PutNextEntry(string name, bool deflated)
    2. {
    3.     this.CloseEntry();
    4.     this.bytes = this.enc.GetBytes("DataChnk");//获得数据区标志
    5.     this.binWriter.Write(this.bytes, 0, 8);//写入标志,每个文件开头都有此标志
    6.     name = name.Replace(@"\", "/").ToLower();//名称处理,替换反斜杠,转为小写
    7.     if (name.StartsWith("/"))
    8.     {
    9.         name = name.Substring(1);
    10.     }
    11.     this.entry = new DataFile.Entry(name);
    12.     this.entry.offset = (uint) this.stream.Position;
    13.     this.entry.flags = DataFile.Entry.FLAGS.NONE;
    14.     if (deflated)
    15.     {
    16.         this.entry.flags = (DataFile.Entry.FLAGS) ((short) (this.entry.flags | DataFile.Entry.FLAGS.COMPRESSED));
    17.     }
    18. }
    复制代码
    创建smpak档的主代码:
    1. private void createArchive(string fullPath, bool makeTemp)
    2. {
    3.     if (!fullPath.EndsWith("course.smpak"))
    4.     {
    5.         throw new ArgumentException("Not a full path to course.smpak!");
    6.     }
    7.     this.closeArchive();
    8.     fullPath = Path.GetDirectoryName(fullPath);
    9.     this.pathDataFile = fullPath;
    10.     Directory.CreateDirectory(this.pathDataFile);
    11.     this.pathOver = Path.Combine(this.pathDataFile, "override");
    12.     this.counter = 0;
    13.     this.writingMode = true;
    14.     this.dfos = new DataFile.OutputStream(this.pathDataFile, makeTemp);
    15. }
    复制代码
    在最后一句调用了OutputStream这一方法,舍得在前面贴出的那些代码正是隶属于OutputStream方法的.

    上面的代码仅仅是提供了一些线索,更具体的内容隐藏在程序之中.现在要制作smpak文件可以走两条路子,一条是利用现成的插件或另外编写个小程序,通过调用SuperMemo的内置函数来进行操作;另一条路是掌握写入smpak文件的规律,编写程序直接处理,这个就要对源程序的更深入地分析才可以.希望能有C#高手来研究这个课题,以便早日实现自己制作course.smpak文件,造福更多的SuperMemo iPhone版用户.




  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 15:06:59 | 显示全部楼层
    关于读取smpak文件,已经有人利用java实现了,这是m4gic推荐的小工具,其地址如下:
    https://github.com/pceccato/smux-anki-converter/

    把其中关系到smpak包读取的两段代码贴出来,供大家参考:
    smpakpaser:
    1. package com.gitgis.sm.smpak;

    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import java.io.FileNotFoundException;
    5. import java.io.IOException;
    6. import java.io.InputStream;
    7. import java.io.RandomAccessFile;
    8. import java.util.Collection;
    9. import java.util.HashMap;
    10. import java.util.logging.Logger;
    11. import java.util.zip.Inflater;
    12. import java.util.zip.InflaterInputStream;

    13. public class SmPakParser implements Parser {

    14.         protected File fileName;
    15.         protected RandomAccessFile randomAccessFile;

    16.         private int entrPos;
    17.         private int namePos;

    18.         private HashMap<String, FileEntry> cachedEntries = new HashMap<String, FileEntry>();

    19.         private SmPakParser(File file) throws SmPakException {
    20.                 this.fileName = file;
    21.                 open();
    22.         }
    23.        
    24.         public static SmPakParser getInstance(File file) throws SmPakException {
    25.                 if (file.exists()) {
    26.                         return new SmPakParser(file);
    27.                 }
    28.                 return null;
    29.         }

    30.         public FileEntry getFileEntry(String fileName) throws IOException,
    31.                         SmPakException {
    32.                 FileEntry retVal = cachedEntries.get(fileName);
    33.                 if (retVal == null) {
    34.                         cachedEntries.put(fileName, null);
    35.                         scanEntrChnk();
    36.                         retVal = cachedEntries.get(fileName);
    37.                 }
    38.                 return retVal;
    39.         }

    40.         private void open() throws SmPakException {

    41.                 try {
    42.                         randomAccessFile = new RandomAccessFile(fileName, "r");
    43.                         // dataInputStream = new DataInputStream(fileInputStream);

    44.                         // int i1;
    45.                         // fileInputStream.skip(30);
    46.                         // i1 = find("DataChnk".getBytes());
    47.                         // System.out.format("%08X %d", i1 ,i1);
    48.                         // System.out.println();

    49.                         readHeader();

    50.                         // 427+28=455 // 01C7
    51.                         // fileInputStream.skip(8);
    52.                         // i1 = find("DataChnk".getBytes());
    53.                         // System.out.format("%08X %d", i1 ,i1);
    54.                         // System.out.println();

    55.                         // DataChnk
    56.                         // byte[] buf = readBuf(8);
    57.                         // System.out.println(new String(buf));

    58.                         // 4877

    59.                         cachedEntries.put("/course.xml", null);
    60.                         // cachedEntries.put("/glossary.xml", null);

    61.                         scanEntrChnk();

    62.                 } catch (FileNotFoundException e) {
    63.                
    64.                         e.printStackTrace();
    65.                 } catch (IOException e) {

    66.                         e.printStackTrace();
    67.                 }

    68.         }

    69.         private void readHeader() throws IOException, SmPakException {
    70.                 // -SMArch-
    71.                 String headerTag = new String(readBuf(8), "ISO-8859-1");

    72.                 if (!headerTag.equals("-SMArch-")) {
    73.                         Logger.getLogger(SmPakParser.class.getName()).info(
    74.                                         "TESTx2 " + headerTag);
    75.                         throw new SmPakException("Invalid SMArch");
    76.                 }

    77.                 // System.out.println(new String(header));
    78.                 // TODO StreamCorruptedException

    79.                 int i1 = readInt(); // TODO
    80.                 entrPos = readInt();
    81.                 namePos = readInt();

    82.                 // fileInputStream.seek(namePos-20-4);
    83.                 randomAccessFile.seek(namePos - 8);
    84.                 String nameHeaderTag = new String(readBuf(8), "ISO-8859-1");

    85.                 if (!nameHeaderTag.equals("NameChnk")) {
    86.                         throw new SmPakException("Invalid NameChnk");
    87.                 }

    88.                 int skipped = 4;
    89.                 readBuf(skipped);
    90.                
    91.                 while (true) { // HACK
    92.                         byte[] buf = readBuf(1);
    93.                         if (buf[0]>='a' && buf[0]<='z') {
    94.                                 break;
    95.                         }
    96.                         skipped++;
    97.                 }

    98.                 namePos = namePos + skipped;
    99.         }

    100.         private void scanEntrChnk() throws IOException, SmPakException {
    101.                 // long curPos = entrPos-8;

    102.                 randomAccessFile.seek(entrPos - 8);

    103.                 String headerTag = new String(readBuf(8), "ISO-8859-1");

    104.                 if (!headerTag.equals("EntrChnk")) {
    105. //                        Logger.getLogger(SmPakParser.class.getName()).info(
    106. //                                        "TEST2 " + headerTag);
    107.                         throw new SmPakException("Invalid EntrChnk");
    108.                 }

    109.                 int filesCnt = readInt();

    110. //                System.out.println(filesCnt);

    111. //                int pos = 28;
    112.                 for (int i = 0; i < filesCnt; i++) {
    113.                         FileEntry fileEntry = new FileEntry();

    114.                         int currentNamePos = this.namePos + readInt(); // 4
    115.                         int nameSize = readShort(); // 2
    116.                         fileEntry.compression = (short) readShort(); // 2
    117.                         fileEntry.filePos = readInt(); // 4
    118.                         fileEntry.fileSize = readInt(); // 4

    119.                         // System.out.println(fileEntry.compression);
    120.                         // curPos+=16;

    121.                         // fileInputStream.reset();
    122.                         // fileInputStream.skip(-curPos);
    123.                         long curPos = randomAccessFile.getFilePointer();
    124.                         randomAccessFile.seek(currentNamePos);
    125.                         fileEntry.name = "/" + new String(readBuf(nameSize), "ISO-8859-1");
    126.                         // fileInputStream.skip(-currentNamePos-nameSize);
    127.                         // fileInputStream.reset();
    128.                         randomAccessFile.seek(curPos);

    129.                         cachedEntries.put(fileEntry.name, fileEntry);
    130.                 }

    131.                 // fileInputStream.reset();
    132.                 // fileInputStream.skip(-4-filesCnt*16-entrPos);
    133.         }

    134.         // private List<FileEntry> readNameChnk(List<FileEntry> list) throws
    135.         // IOException {
    136.         // // 4E 61 6D 65 43 68 6E 6B 58 04 00 00 D8 08
    137.         // // 4E 61 6D 65 43 68 6E 6B CD 02 00 00 CD 05
    138.         // // 4E 61 6D 65 43 68 6E 6B FC FA 01 00 FC F5 07
    139.         // // N a m e c h n k chunksize TODO
    140.         //
    141.         // fileInputStream.skip(namePos-8);
    142.         // int chunkSize = readInt();
    143.         // int skipped = 0;
    144.         //
    145.         // while(((readBuf(1)[0])&0xFF)>=0xF) skipped++; // HACK
    146.         // skipped++;
    147.         //
    148.         // int cnt=0;
    149.         // for (FileEntry fileEntry: list) {
    150.         // fileEntry.name = "/"+new String(readBuf(fileEntry.nameSize));
    151.         // cnt+=fileEntry.nameSize;
    152.         // }
    153.         //
    154.         // fileInputStream.skip(8-namePos-cnt-4-skipped);
    155.         // return list;
    156.         // }

    157.         protected byte[] readBuf(int length) throws IOException {
    158.                 byte[] buf = new byte[length];
    159.                 randomAccessFile.read(buf, 0, buf.length);
    160.                 return buf;

    161.         }

    162.         protected int readInt() throws IOException {
    163.                 byte[] buf = new byte[4];
    164.                 randomAccessFile.read(buf, 0, buf.length);
    165.                 return ((buf[3] & 0xff) << 24) + ((buf[2] & 0xff) << 16)
    166.                                 + ((buf[1] & 0xff) << 8) + ((buf[0] & 0xff) << 0);
    167.         }

    168.         protected int readInt2() throws IOException {
    169.                 byte[] buf = new byte[4];
    170.                 randomAccessFile.read(buf, 0, buf.length);
    171.                 return ((buf[0] & 0xff) << 24) + ((buf[1] & 0xff) << 16)
    172.                                 + ((buf[2] & 0xff) << 8) + ((buf[3] & 0xff) << 0);
    173.         }

    174.         protected int readShort2() throws IOException {
    175.                 byte[] buf = new byte[2];
    176.                 randomAccessFile.read(buf, 0, buf.length);
    177.                 return ((buf[1] & 0xff) << 0) + ((buf[0] & 0xff) << 8);
    178.         }

    179.         protected int readShort() throws IOException {
    180.                 byte[] buf = new byte[2];
    181.                 randomAccessFile.read(buf, 0, buf.length);
    182.                 return ((buf[0] & 0xff) << 0) + ((buf[1] & 0xff) << 8);
    183.         }

    184.         public InputStream getInputStream(String entryName) throws IOException, SmPakException {
    185.                 FileEntry fileEntry = getFileEntry(entryName);
    186.                 if (fileEntry == null) {
    187.                         return null;
    188.                 }
    189.                 return getInputStream(fileEntry);
    190.         }

    191.         private InputStream getInputStream(FileEntry fileEntry) throws IOException {
    192.                 InputStream retVal = new FileInputStream(fileName);
    193.                 retVal.skip(fileEntry.filePos);

    194.                 retVal = new EntryInputStream(retVal, fileEntry.fileSize);

    195.                 if (fileEntry.compression == FileEntry.INFLATE) {
    196.                         Inflater decompresser = new Inflater(true);
    197.                         retVal = new InflaterInputStream(retVal, decompresser,
    198.                                         fileEntry.fileSize);
    199.                 }
    200.                 return retVal;

    201.         }

    202.         public HashMap<String, String> getGlossary() throws SmPakException {
    203.                 HashMap<String, String> glossary = null;
    204.                 try {
    205.                         glossary = new Glossary(
    206.                                         getInputStream(getFileEntry("/glossary/glossary.xml")));
    207.                 } catch (IOException e) {

    208.                         e.printStackTrace();
    209.                 }
    210.                 return glossary;
    211.         }

    212.         /**
    213.          * @return
    214.          * @throws SmPakException
    215.          * @throws IOException
    216.          */
    217.         public Collection<String> getFileEntryNames() throws IOException,
    218.                         SmPakException {
    219.                 scanEntrChnk();
    220.                 return cachedEntries.keySet();
    221.         }

    222. }
    复制代码

    smparser:
    1. /**
    2. *
    3. */
    4. package com.gitgis.sm.smpak;

    5. import java.io.File;
    6. import java.io.IOException;
    7. import java.io.InputStream;
    8. import java.util.Collection;
    9. import java.util.HashSet;
    10. import java.util.Set;
    11. import java.io.FileInputStream;

    12. import com.gitgis.sm.course.Course;

    13. /**
    14. * @author gg
    15. *
    16. */
    17. public class SmParser implements Parser {

    18.         private SmPakParser diffParser;
    19.         private SmPakParser baseParser;
    20.         private File courseDir;
    21.        
    22.         public SmParser(File dir, String file ) throws SmPakException
    23.         {
    24.                 courseDir = dir;
    25.                 baseParser = SmPakParser.getInstance(new File(courseDir, file + ".smpak"));
    26.                 diffParser = SmPakParser.getInstance(new File(courseDir, file + ".smdif"));
    27.         }


    28.         public Course getCourse() throws SmPakException {
    29.                 Course course = null;
    30.                 try {
    31.                         course = new Course(this, getInputStream("/course.xml"));
    32.                 } catch (IOException e) {

    33.                         e.printStackTrace();
    34.                 }
    35.                 return course;
    36.         }

    37.         /**
    38.          * @return
    39.          * @throws SmPakException
    40.          * @throws IOException
    41.          */
    42.         public Collection<String> getFileEntryNames() throws IOException, SmPakException {
    43.                 Set<String> retVal = new HashSet<String>();
    44.                
    45.                 retVal.addAll(baseParser.getFileEntryNames());
    46.                 if (diffParser != null) {
    47.                         retVal.addAll(diffParser.getFileEntryNames());
    48.                 }
    49.                
    50.                 return retVal;
    51.         }


    52.         /* (non-Javadoc)
    53.          * @see com.gitgis.sm.smpak.Parser#getInputStream(java.lang.String)
    54.          */
    55.         @Override
    56.         public InputStream getInputStream(String entryName) throws IOException,
    57.                         SmPakException {
    58.                 InputStream inputStream = null;
    59.                 File overrideDir = new File(courseDir, "override");
    60.                 if( overrideDir.exists() && overrideDir.isDirectory() )        {
    61.                         //
    62.                         // get the stream from the override dir rather than from within the smpak file
    63.                         //
    64.                         File courseXml = new File(overrideDir, entryName );
    65.                         if( courseXml.isFile() ){
    66.                                 FileInputStream courseStream = new FileInputStream( courseXml );
    67.                                 inputStream = courseStream;
    68.                         }
    69.                 }
    70.                 if( inputStream == null ){
    71.                         //
    72.                         // failed to read from override dir - fallback to reading from SMPAK file
    73.                         //
    74.                         if (diffParser != null){
    75.                                 inputStream = diffParser.getInputStream(entryName);
    76.                         }
    77.                         if (inputStream == null){
    78.                                 inputStream = baseParser.getInputStream(entryName);
    79.                         }
    80.                 }
    81.                 return inputStream;
    82.         }
    83. }
    复制代码



  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 15:22:05 | 显示全部楼层
    回到SuperMemo主程序代码,再贴一段数据处理的代码,舍得推测,这段代码的主要作用是处理item*.xml文件:
    1. public Stream getOutputStream(string name)
    2. {
    3.     if (this.writingMode)
    4.     {
    5.         string str2;
    6.         if (((str2 = Path.GetExtension(name).ToLower()) != null) && ((str2 == ".xml") || (str2 == ".css")))
    7.         {
    8.             this.dfos.PutNextEntry(name, true);
    9.             return new DeflateStream(this.dfos, CompressionMode.Compress, true);
    10.         }
    11.         this.dfos.PutNextEntry(name);
    12.         return this.dfos;
    13.     }
    14.     this.path1 = Path.Combine(this.pathOver, name);
    15.     Directory.CreateDirectory(Path.GetDirectoryName(this.path1));
    16.     return new FileStream(this.path1, FileMode.Create);
    17. }
    复制代码

    这里貌似调用了C#的CompressionMode.


  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 15:28:49 | 显示全部楼层
    下面这段代码很有意思,它提醒了我原来的思路有些偏差:
    1. public void openArchive(string fullPath)
    2. {
    3.     if (!fullPath.EndsWith("course.smpak"))
    4.     {
    5.         throw new ArgumentException("Not a full path to archive file!");
    6.     }
    7.     this.closeArchive();
    8.     this.pathDataFile = Path.GetDirectoryName(fullPath);
    9.     this.pathOver = Path.Combine(this.pathDataFile, "override");
    10.     this.counter = 0;
    11.     this.writingMode = false;
    12.     this.dfis = new DataFile.InputStream(fullPath);
    13.     this.pathPatch = Path.Combine(this.pathDataFile, "course.smdif");
    14.     if (File.Exists(this.pathPatch))
    15.     {
    16.         this.dfisPatch = new DataFile.InputStream(this.pathPatch);
    17.     }
    18.     this.readOriginalNameList();
    19. }

    复制代码
    从上面可以看到,程序要将course.smdif(科普一下,SM官方经常会发一些课程的补丁,smdif正是这种补丁)里的内容输入到course.smpak中,它调用的是inputStream这个东东.之前我一直一厢情愿地认为制作smpak包当然要用createArchive,用插件系统调用了createArchive,结果它把原来的那个文件给清空了.现在想想不清空才怪,人家是"创建",完全有可能是创建一个空文件.
    现在我们手头已经有course.smpak文件,这里有是有内容的,其主体就是程序自带的模板文件.那么,我们来把思路小小调整一下,能否用inputStream这一方法往这个course.smpak里写东西呢?



  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 15:45:45 | 显示全部楼层
    InputStream的代码如下:

    1. public InputStream(string path)
    2. {
    3.     this.enc = new ASCIIEncoding();//编码
    4.     try
    5.     {
    6.         this.stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
    7.     }
    8.     catch (UnauthorizedAccessException)
    9.     {
    10.         this.stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    11.     }
    12.     this.binReader = new BinaryReader(this.stream);
    13.     this.bytes = new byte[8];//读头8个字节
    14.     this.binReader.Read(this.bytes, 0, 8);
    15.     this.str = this.enc.GetString(this.bytes);
    16.     if (!this.str.Equals("-SMArch-"))//如果头8个字节不是-SMArch-,则报文件错误
    17.     {
    18.         throw new FormatException(Txt.get("datafile.error.file"));
    19.     }
    20.     if (this.binReader.ReadInt16() > 0x101)//检查9,10两位,如果发现大于101,则报版本错误
    21.     {
    22.         throw new FormatException(Txt.get("datafile.error.version"));
    23.     }
    24.     this.flags = (DataFile.ARCHFLAGS) this.binReader.ReadInt16();//接着往下读2个字节,为压缩标记
    25.     this.entriesOffset = this.binReader.ReadUInt32();//接着往下读4个字节 ,为内容区偏移值
    26.     this.namesOffset = this.binReader.ReadUInt32();//接着往下读4个字节,为名称区偏移值
    27.     this.stream.Position = this.namesOffset;//读取名称偏移值
    28.     this.binReader.ReadUInt32();
    29.     this.names = this.binReader.ReadString();
    30.     this.stream.Position = this.entriesOffset;//读取词条内容偏移值
    31.     int capacity = this.binReader.ReadInt32();//读取文件容量值
    32.     this.entries = new List<DataFile.Entry>(capacity);
    33.     this.deleted = new List<DataFile.Entry>(5);
    34.     for (int i = 0; i < capacity; i++)
    35.     {
    36.         this.entry = DataFile.Entry.read(this.binReader, this.names);
    37.         if (((short) (this.entry.flags & DataFile.Entry.FLAGS.DELETED)) == 0)
    38.         {
    39.             this.entries.Add(this.entry);//添加内容
    40.         }
    41.         else
    42.         {
    43.             this.deleted.Add(this.entry);
    44.         }
    45.     }
    46.     this.deleted.Sort();
    47. }

    复制代码


  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 17:05:31 | 显示全部楼层
    下面就是解包用的代码unpackFile,michael的插件提取官方课程包文件调用的就是这段代码.

    1. public int unpackFile(string name, bool throwOnFileExists)
    2. {
    3.     int num = 0;
    4.     string path = Path.Combine(this.pathOver, name);
    5.     if (File.Exists(path))
    6.     {
    7.         if (throwOnFileExists)
    8.         {
    9.             throw new ArgumentException("File exists!", name);
    10.         }
    11.         return 0;
    12.     }
    13.     using (Stream stream = this.getInputStream(name, true))//根据文件名从包中取出该文件的内容,getInputStream这个函数很有用哦!
    14.     {
    15.         if (stream == null)
    16.         {
    17.             return num;
    18.         }
    19.         this.path1 = Path.GetDirectoryName(path);
    20.         Directory.CreateDirectory(this.path1);
    21.         using (Stream stream2 = File.Create(path))
    22.         {
    23.             num = this.copyStream(stream, stream2);//将得到的文件"复制"出来.
    24.             stream2.Close();
    25.         }
    26.         stream.Close();
    27.     }
    28.     return num;
    29. }

    复制代码

    看一看michael写的提取文件的代码:
    1.         public override void Execute()
    2.         {
    3.             string[] storage = SuperMemo.ListFilesForCurrentStorage(string.Empty, true);
    4.             var files = new List<string>(storage);//获得文件名的列表

    5.             string selectedFile = DlgSelectItem<string>.SelectItem("Archive files SmPack", "Select File", files, 0);//选定文件,那个插件一次只允许提取一个.其实这里改写一下,用一个for循环,就可以把所有的文件都提取出来.
    6.             if (!string.IsNullOrEmpty(selectedFile))
    7.             {
    8.                 SuperMemo.UnpackFile(selectedFile, false);//解包
    9.             }
    10.         }
    复制代码



  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 17:11:54 | 显示全部楼层
    这一段是根据文件名从smpak中提取文件内容的代码,所提取的内容为stream,需要再调用copyStream将文件复制出来:

    1. public Stream getInputStream(string name, bool throwOnFileNotFound, bool skipPatch)
    2. {
    3.     if (name != null)
    4.     {
    5.         string str = name.Replace('/', '.');
    6.         for (int i = 0; i < this.resourceNames.Length; i++)
    7.         {
    8.             if (this.resourceNames[i].EndsWith(str) && this.resourceNames[i].ToLower().StartsWith("smw.resources."))
    9.             {
    10.                 return this.thisExe.GetManifestResourceStream(this.resourceNames[i]);
    11.             }
    12.         }
    13.         return this.getExternalInputStream(name, throwOnFileNotFound, skipPatch);
    14.     }
    15.     if (throwOnFileNotFound)
    16.     {
    17.         throw new FileNotFoundException("", name);
    18.     }
    19.     return null;
    20. }

    复制代码

    copyStream的代码:
    1. public static int copyStream(Stream src, Stream dest, byte[] buf)
    2. {
    3.     int count = 0;
    4.     int num2 = 0;
    5.     do
    6.     {
    7.         count = src.Read(buf, 0, buf.Length);
    8.         dest.Write(buf, 0, count);
    9.         num2 += count;
    10.     }
    11.     while (count > 0);
    12.     return num2;
    13. }

    复制代码




  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 19:40:51 | 显示全部楼层
    解包的操作已经成功,刚才将michael的插件修改了一下,原来一次只能提取一个文件的,现在修改成一次性提取当前课程的所有文件.这样一来,我们可以把官方课程的课程直接提取出来.

    刚才测试了一下,将官方的一个Extreme English课程中的所有文件全部提取出来了.提取完之后吓一跳,源文件(course.smpak)只有334M,提取出来之后容量达到1.15G,其中媒体文件达到958M(这里边包括249M的图片,其余为MP3文件),我试着用7z对里边的mp3进行打包,发现压缩率并不高,那么,SuperMemo官方是如何做到的?莫非是将mp3文件转成了byte,这样会大大减少空间?
  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-4 21:42:54 | 显示全部楼层
    create代码,这里涉及到写入:
    1. public static Storage create(string dirName, bool withTemplates, bool locked)
    2. {
    3.     string path = dirName;
    4.     if (!path.StartsWith(defaultPath))
    5.     {
    6.         path = Path.Combine(defaultPath, dirName);
    7.     }
    8.     if (path.EndsWith("course.smpak"))
    9.     {
    10.         path = Path.GetDirectoryName(path);
    11.     }
    12.     if (VistaSecurity.isVista && !VistaSecurity.isAdmin)
    13.     {
    14.         path = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "MyCourses"), dirName);
    15.     }
    16.     if (Directory.Exists(path))
    17.     {
    18.         throw new DirectoryNotFoundException();
    19.     }
    20.     path = Path.Combine(path, "course.smpak");
    21.     Storage storage = new Storage();
    22.     Directory.CreateDirectory(Path.GetDirectoryName(path));
    23.     using (FileStream stream = File.OpenWrite(path))
    24.     {
    25.         using (BinaryWriter writer = new BinaryWriter(stream))
    26.         {
    27.             writer.Write(withTemplates ? Resources.templates : Resources.empty);
    28.             writer.Close();
    29.         }
    30.     }
    31.     if (locked)
    32.     {
    33.         try
    34.         {
    35.             BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite));
    36.             reader.Read(new byte[8], 0, 8);
    37.             reader.ReadInt16();
    38.             DataFile.ARCHFLAGS archflags = (DataFile.ARCHFLAGS) reader.ReadInt16();
    39.             reader.Close();
    40.             BinaryWriter writer2 = new BinaryWriter(File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite));
    41.             writer2.Seek(10, SeekOrigin.Begin);
    42.             archflags = (DataFile.ARCHFLAGS) ((short) (archflags | DataFile.ARCHFLAGS.LOCKED));
    43.             writer2.Write((short) archflags);
    44.             writer2.Close();
    45.         }
    46.         catch (Exception exception)
    47.         {
    48.             Txt.log("Storage.create", exception.Message);
    49.         }
    50.     }
    51.     storage.openArchive(path);
    52.     return storage;
    53. }
    复制代码


  • TA的每日心情
    开心
    2011-10-10 00:27
  • 签到天数: 68 天

    [LV.6]常住居民II

     楼主| 舍得 发表于 2011-9-11 15:35:05 | 显示全部楼层
    列举文件的代码:

    1. public string[] listFiles(string startWith, bool sort)
    2. {
    3.     List<string> list = new List<string>(0x3e8);
    4.     List<string> collection = new List<string>(0x3e8);
    5.     if (this.pathOver != null)
    6.     {
    7.         string fileName;
    8.         if (startWith.IndexOfAny(new char[] { '/', '\\' }) > -1)
    9.         {
    10.             this.path2 = Path.Combine(this.pathOver, startWith);
    11.             this.path1 = Path.GetDirectoryName(this.path2);
    12.             fileName = Path.GetFileName(this.path2);
    13.         }
    14.         else
    15.         {
    16.             this.path1 = this.pathOver;
    17.             fileName = startWith;
    18.         }
    19.         if (Directory.Exists(this.pathOver) && Directory.Exists(this.path1))
    20.         {
    21.             FileSystemInfo[] fileSystemInfos = new DirectoryInfo(this.path1).GetFileSystemInfos(fileName + "*");
    22.             List<string> names = new List<string>();
    23.             this.listFiles(fileSystemInfos, ref names, fileName + "*");
    24.             int startIndex = this.pathOver.Length + 1;
    25.             for (int i = 0; i < names.Count; i++)
    26.             {
    27.                 if (names[i].StartsWith(this.pathOver))
    28.                 {
    29.                     string item = names[i].Substring(startIndex).Replace(@"\", "/");
    30.                     list.Add(item);
    31.                 }
    32.             }
    33.         }
    34.     }
    35.     foreach (Smdif smdif in (IEnumerable<Smdif>) this.smdifs)
    36.     {
    37.         if (smdif.Dfis != null)
    38.         {
    39.             smdif.Dfis.SetEnumFilter(startWith);
    40.             foreach (DataFile.Entry entry in smdif.Dfis)
    41.             {
    42.                 if (!list.Contains(entry.name))
    43.                 {
    44.                     list.Add(entry.name);
    45.                 }
    46.             }
    47.             continue;
    48.         }
    49.     }
    50.     if (this.dfis != null)
    51.     {
    52.         this.dfis.SetEnumFilter(startWith);
    53.         foreach (DataFile.Entry entry2 in this.dfis)
    54.         {
    55.             if (!list.Contains(entry2.name) && !collection.Contains(entry2.name))
    56.             {
    57.                 collection.Add(entry2.name);
    58.             }
    59.         }
    60.     }
    61.     list.AddRange(collection);
    62.     if (sort)
    63.     {
    64.         list.Sort();
    65.     }
    66.     return list.ToArray();
    67. }

    复制代码

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    小黑屋|手机版|Archiver|官方微博|官方QQ群|舍得英语魔法学苑 ( 冀ICP备11024081号-1 )

    GMT+8, 2017-11-19 09:22 , Processed in 0.488665 second(s), 20 queries .

    Powered by Discuz! X3.2

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表