import java.io.*;

/* RealMedia file repair file.
 * ©Vidar Holen, 2003
 * www.VidarHolen.net
 * Released under the GNU General Public License.
 * Disclaimer:  Don't blame me for anything, it'll be
 *              all your fault. 
 * 
 * Description:
 * This program will attempt to fix corrupted RealMedia files.
 * It does this by scanning the file for broken Media Packets,
 * setting the last valid packet's length to cover the corruption
 * and giving it a bogus stream number. 
 *
 * RealPlayer doesn't like this technique, but MPlayer works mostly
 * fine (with skips obviously). It even syncs properly afterwards.
 * Sometimes audio will be broken for the rest of the movie, so 
 * don't get your hopes up too high.
 *
 * Make sure you backup files, this program will edit the old file,
 * not copy it.
 *
 * 
 * Vidar
 * 
 */

public class RealFix {
    RandomAccessFile raf;
    final public static int RMF=0x2E524D46, PROP=0x50524F50, MDPR=0x4D445052, CONT=0x434F4E54, DATA=0x44415441, INDX=0x494E4458;
    final public static int REQPACK=6; //required valid packets in a row
    final public static int BOGSTREAM=23038; //stream num to set invalids to
    boolean debug=false;
    long endData;
    long nextData;

    public RealFix(String file) throws IOException {
        open(file);
    }
    public RealFix() {}
    public void open(String file) throws IOException {
        try {
            raf=new RandomAccessFile(file,"rw");
        } catch(FileNotFoundException e) {
            if(debug) raf=new RandomAccessFile(file,"r");
            else throw e;
        }
    }
    public void dbg(String b) {
        if(debug) System.err.println(b);
    }
    public void msg(String b) {
        System.out.println(b);
    }

    public void fix() throws IOException {
        int type, size;
        do {
            while((type=raf.readInt())!=DATA) {
                switch(type) {
                case RMF:
                case PROP:
                case MDPR:
                case CONT:
                case INDX:
                    break;
                default:
                    dbg("File is fundamentally borked. Giving up.");
                    throw new IOException("File borked too deeply.");
                }
                size=raf.readInt();
                raf.skipBytes(size-2*4);
            }
            dbg("Found data chunk at "+(raf.getFilePointer()-4));
            endData=raf.getFilePointer()+raf.readInt()-4;
            int version=raf.readUnsignedShort();
            if(version!=0)
                throw new IOException("Bad DataChunk Object Version: "+version);
            int numpack=raf.readInt();
            dbg("There are "+numpack+" packets.");
            nextData=raf.readInt();
            dbg("Next Data Chunk: "+nextData);
            dbg("Starting packet reading.");

            MediaPacket mp;
            long lastValid=-1;
            while(true) {
                mp=readMedia();
                if(mp.isValid()) {
                    lastValid=raf.getFilePointer()-12;
                    raf.skipBytes(mp.length-12);
                } else {
                    if(raf.getFilePointer()>endData) break;
                    msg("Invalid packet at "+mp.offset);
                    dbg("Found invalid packet at "+mp.offset+":");
                    dbg(mp.toString());
                    if(lastValid==-1) {
                        dbg("There have been no valid packets. I can't do this.");
                        throw new IOException("File too borked.");
                    }
                    long next=searchPacket();
                    int skip=(int)(next-lastValid);
                    msg("Found valid packet "+skip+" bytes from here");
                    raf.seek(lastValid);
                    while(skip>0) {
                        int nextsize;
                        if(skip>65535) {
                            nextsize=65535;
                            if((skip-nextsize)<12) nextsize-=12;
                        } else nextsize=skip;
                        dbg("Making record of "+nextsize+"b.");
                        raf.writeShort(0); //type
                        raf.writeShort(nextsize);
                        raf.writeShort(BOGSTREAM);
                        raf.skipBytes(nextsize-6);
                        skip-=nextsize;
                        dbg(skip+" bytes to go.");
                    }
                    raf.seek(lastValid);
                    msg("Corrected error.");
                }
            }
            dbg("End of data chunk");
            raf.seek(nextData);
        } while(nextData!=0);
        msg("Finished.");
    }

    public MediaPacket readMedia() throws IOException {
        MediaPacket mp=new MediaPacket();
        mp.offset=raf.getFilePointer();
        mp.version=raf.readUnsignedShort();
        if(mp.version==0) {
            mp.length=raf.readUnsignedShort();
            mp.stream=raf.readUnsignedShort();
            mp.ts=raf.readInt();
            mp.res=raf.read();
            mp.flag=raf.read();
        }
        return mp;
    }

    public long searchPacket() throws IOException {
        long here;
        int ok=0;
        while(ok>=0) {
            here=raf.getFilePointer();
            ok=0;
            MediaPacket mp;
            while((mp=readMedia()).isValid() && ok<REQPACK) {
                ok++;
                dbg("Media packets valid: #"+ok);
                dbg(mp.toString());
                raf.skipBytes(mp.length-12);
            }
            if(ok==REQPACK) {
                dbg(ok+" valid packets in a row, allright!");
                return here;
            } else if(ok>0) {
                dbg("Stopped at #"+ok+", invalid:");
                dbg(mp.toString());
            }
            raf.seek(here+1);
        }
        return -1;
    }
    public void close() throws IOException {
        raf.close();
    }
    
    public void setDebug(boolean b) {
        debug=b;
    }

    class MediaPacket {
        long offset;
        int version;
        int length, stream;
        int ts;
        int res,flag;
        public boolean isValid() {
            if(version!=0) return false;
            if(length<12 || (length>4000 && stream!=BOGSTREAM)) return false;
            if(stream!=BOGSTREAM && stream>12) return false;
            return true;
        }
        public String toString() {
            return "Packet: off="+offset+", version="+version+", length="+length+", stream="+stream;
        }
    }

    public static void main(String[] args) throws IOException {
        String fn=null;
        boolean v=false;
        RealFix rf=new RealFix();
        for(int i=0; i<args.length; i++) {
            if(args[i].equals("-v")) {
                rf.setDebug(true);
            } else {
                rf.open(args[i]);
                rf.fix();
                rf.close();
            }
        }
    }
}

