Пример #1
0
        /// <summary>
        /// Parses an OMF segment header.  If successful, a new OmfSegment object is created.
        /// </summary>
        /// <param name="data">File data.</param>
        /// <param name="offset">Offset at which to start parsing.</param>
        /// <param name="parseAsLibrary">Set to true to parse the header as if it were part
        ///   of a library file.  Affects parsing of v1 headers.</param>
        /// <param name="msgs">Notes and errors generated by the parser.</param>
        /// <param name="segResult">Completed object, or null on failure.</param>
        /// <returns>Result code.</returns>
        public static ParseResult ParseHeader(byte[] data, int offset, bool parseAsLibrary,
                                              List <string> msgs, out OmfSegment segResult)
        {
            segResult = null;

            //Debug.WriteLine("PARSE offset=" + offset);

            Debug.Assert(offset < data.Length);
            if (data.Length - offset < MIN_HEADER_V0)
            {
                // Definitely too small.
                AddErrorMsg(msgs, offset, "remaining file space too small to hold segment");
                return(ParseResult.Failure);
            }

            OmfSegment newSeg = new OmfSegment();

            newSeg.mFileData  = data;
            newSeg.FileOffset = offset;

            // Start with the version number.  The meaning of everything else depends on this.
            int minLen, expectedDispName;

            switch (data[offset + 0x0f])
            {
            case 0:
                newSeg.Version   = SegmentVersion.v0_0;
                minLen           = MIN_HEADER_V0;
                expectedDispName = 0x24;
                break;

            case 1:
                newSeg.Version   = SegmentVersion.v1_0;
                minLen           = MIN_HEADER_V1;
                expectedDispName = 0x2c;
                break;

            case 2:
                newSeg.Version   = SegmentVersion.v2_0;
                minLen           = MIN_HEADER_V2;
                expectedDispName = 0x2c;
                break;

            default:
                // invalid version, this is probably not OMF
                AddErrorMsg(msgs, offset, "invalid segment type " + data[offset + 0x0f]);
                return(ParseResult.Failure);
            }
            if (data.Length - offset < minLen)
            {
                // Too small for this version of the header.
                AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
                            newSeg.Version + " segment");
                return(ParseResult.Failure);
            }

            int blkByteCnt = RawData.GetWord(data, offset + 0x00, 4, false);

            newSeg.ResSpc = RawData.GetWord(data, offset + 0x04, 4, false);
            newSeg.Length = RawData.GetWord(data, offset + 0x08, 4, false);
            newSeg.LabLen = data[offset + 0x0d];
            int numLen = data[offset + 0x0e];

            newSeg.BankSize = RawData.GetWord(data, offset + 0x10, 4, false);
            int numSex, dispName;

            if (newSeg.Version == SegmentVersion.v0_0)
            {
                newSeg.Org   = RawData.GetWord(data, offset + 0x14, 4, false);
                newSeg.Align = RawData.GetWord(data, offset + 0x18, 4, false);
                numSex       = data[offset + 0x1c];
                // 7 unused bytes follow
                dispName = 0x24;
                if (newSeg.LabLen == 0)
                {
                    newSeg.DispData = dispName + data[offset + dispName];
                }
                else
                {
                    newSeg.DispData = dispName + LOAD_NAME_LEN;
                }
            }
            else
            {
                newSeg.BankSize = RawData.GetWord(data, offset + 0x10, 4, false);
                newSeg.Org      = RawData.GetWord(data, offset + 0x18, 4, false);
                newSeg.Align    = RawData.GetWord(data, offset + 0x1c, 4, false);
                numSex          = data[offset + 0x20];
                newSeg.LcBank   = data[offset + 0x21];  // v1.0 only
                newSeg.SegNum   = RawData.GetWord(data, offset + 0x22, 2, false);
                newSeg.Entry    = RawData.GetWord(data, offset + 0x24, 4, false);
                dispName        = RawData.GetWord(data, offset + 0x28, 2, false);
                newSeg.DispData = RawData.GetWord(data, offset + 0x2a, 2, false);
            }

            // The only way to detect a v2.1 segment is by checking DISPNAME.
            if (newSeg.Version == SegmentVersion.v2_0 && dispName > 0x2c)
            {
                newSeg.Version    = SegmentVersion.v2_1;
                expectedDispName += 4;

                if (data.Length - offset < minLen + 4)
                {
                    AddErrorMsg(msgs, offset, "remaining file space too small to hold " +
                                newSeg.Version + " segment");
                    return(ParseResult.Failure);
                }
                newSeg.TempOrg = RawData.GetWord(data, offset + 0x2c, 4, false);
            }

            // Extract Kind and its attributes.  The Orca/M 2.0 manual refers to the 1-byte
            // field in v0/v1 as "TYPE" and the 2-byte field as "KIND", but we're generally
            // following the GS/OS reference nomenclature.
            int kindByte, kindWord;

            if (newSeg.Version <= SegmentVersion.v1_0)
            {
                kindByte = data[offset + 0x0c];
                if (!Enum.IsDefined(typeof(SegmentKind), kindByte & 0x1f))
                {
                    // Example: Moria GS has a kind of $1F for its GLOBALS segment.
                    AddErrorMsg(msgs, offset, "invalid segment kind $" + kindByte.ToString("x2"));
                    return(ParseResult.Failure);
                }
                newSeg.Kind = (SegmentKind)(kindByte & 0x1f);

                int kindAttrs = 0;
                if ((kindByte & 0x20) != 0)
                {
                    kindAttrs |= (int)SegmentAttribute.PosnIndep;
                }
                if ((kindByte & 0x40) != 0)
                {
                    kindAttrs |= (int)SegmentAttribute.Private;
                }
                if ((kindByte & 0x80) != 0)
                {
                    kindAttrs |= (int)SegmentAttribute.Dynamic;
                }
                newSeg.Attrs = (SegmentAttribute)kindAttrs;
            }
            else
            {
                // Yank all the attribute bits out at once.  Don't worry about v2.0 vs. v2.1.
                kindWord = RawData.GetWord(data, offset + 0x14, 2, false);
                if (!Enum.IsDefined(typeof(SegmentKind), kindWord & 0x001f))
                {
                    AddErrorMsg(msgs, offset, "invalid segment kind $" + kindWord.ToString("x4"));
                    return(ParseResult.Failure);
                }
                newSeg.Kind  = (SegmentKind)(kindWord & 0x001f);
                newSeg.Attrs = (SegmentAttribute)(kindWord & 0xff00);
            }

            // If we found a library dictionary segment, and we're not currently handling the
            // file as a library, reject this and try again.
            if (newSeg.Kind == SegmentKind.LibraryDict && !parseAsLibrary)
            {
                AddInfoMsg(msgs, offset, "found Library Dictionary segment, retrying as library");
                return(ParseResult.IsLibrary);
            }

            // We've got the basic pieces.  Handle the block-vs-byte debacle.
            int  segLen;
            bool asBlocks = false;

            if (newSeg.Version == SegmentVersion.v0_0)
            {
                // Always block count.
                segLen   = blkByteCnt * DISK_BLOCK_SIZE;
                asBlocks = true;
            }
            else if (newSeg.Version >= SegmentVersion.v2_0)
            {
                // Always byte count.
                segLen = blkByteCnt;
            }
            else   /*v1.0*/
            {
                // Only Library files should treat the field as bytes.  We can eliminate Load
                // files by checking for a nonzero SegNum field, but there's no reliable way
                // to tell the difference between Object and Library while looking at a segment
                // in isolation.
                if (parseAsLibrary)
                {
                    segLen = blkByteCnt;
                }
                else
                {
                    segLen   = blkByteCnt * DISK_BLOCK_SIZE;
                    asBlocks = true;
                }
            }
            newSeg.RawFileLength = newSeg.FileLength = segLen;

            //
            // Perform validity checks.  If any of these fail, we're probably reading something
            // that isn't OMF (or, if this isn't the first segment, we might have gone off the
            // rails at some point).
            //

            if (numLen != 4)
            {
                AddErrorMsg(msgs, offset, "NUMLEN must be 4, was " + numLen);
                return(ParseResult.Failure);
            }
            if (numSex != 0)
            {
                AddErrorMsg(msgs, offset, "NUMSEX must be 0, was " + numSex);
                return(ParseResult.Failure);
            }
            if (offset + segLen > data.Length)
            {
                if (asBlocks && offset + segLen - data.Length < DISK_BLOCK_SIZE)
                {
                    // I have found a few examples (e.g. BRIDGE.S16 in Davex v1.23, SYSTEM:START
                    // on an old Paintworks GS disk) where the file's length doesn't fill out
                    // the last block in the file.  If we continue, and the segment actually
                    // does pass EOF, we'll fail while reading the records.
                    AddInfoMsg(msgs, offset,
                               "file EOF is not a multiple of 512; last segment may be truncated");
                    newSeg.FileLength = data.Length - offset;
                }
                else
                {
                    // Segment is longer than the file.  (This can happen easily in a static lib if
                    // we're not parsing it as such.)
                    AddErrorMsg(msgs, offset, "segment file length exceeds EOF (segLen=" + segLen +
                                ", remaining=" + (data.Length - offset) + ")");
                    return(ParseResult.Failure);
                }
            }
            if (dispName < expectedDispName || dispName > (segLen - LOAD_NAME_LEN))
            {
                AddErrorMsg(msgs, offset, "invalid DISPNAME " + dispName + " (expected " +
                            expectedDispName + ", segLen=" + segLen + ")");
                return(ParseResult.Failure);
            }
            if (newSeg.DispData < expectedDispName + LOAD_NAME_LEN ||
                newSeg.DispData > (segLen - 1))
            {
                AddErrorMsg(msgs, offset, "invalid DISPDATA " + newSeg.DispData + " (expected " +
                            (expectedDispName + LOAD_NAME_LEN) + ", segLen=" + segLen + ")");
                return(ParseResult.Failure);
            }
            if (newSeg.BankSize > 0x00010000)
            {
                AddErrorMsg(msgs, offset, "invalid BANKSIZE $" + newSeg.BankSize.ToString("x"));
                return(ParseResult.Failure);
            }
            if (newSeg.Align > 0x00010000)
            {
                AddErrorMsg(msgs, offset, "invalid ALIGN $" + newSeg.Align.ToString("x"));
                return(ParseResult.Failure);
            }

            if (newSeg.BankSize != 0x00010000 && newSeg.BankSize != 0)
            {
                // This is fine, just a little weird.
                AddInfoMsg(msgs, offset, "unusual BANKSIZE $" + newSeg.BankSize.ToString("x6"));
            }
            if (newSeg.Align != 0 && newSeg.Align != 0x0100 && newSeg.Align != 0x00010000)
            {
                // Unexpected; the loader will round up.
                AddInfoMsg(msgs, offset, "unusual ALIGN $" + newSeg.Align.ToString("x6"));
            }
            if (newSeg.Entry != 0 && newSeg.Entry >= newSeg.Length)
            {
                // This is invalid, but if we got this far we might as well keep going.
                AddInfoMsg(msgs, offset, "invalid ENTRY $" + newSeg.Entry.ToString("x6"));
            }

            // Extract LOADNAME.  Fixed-width field, padded with spaces.  Except for the
            // times when it's filled with zeroes instead.
            string loadName     = string.Empty;
            int    segNameStart = dispName;

            if (newSeg.Version != SegmentVersion.v0_0)
            {
                loadName      = ExtractString(data, offset + dispName, LOAD_NAME_LEN);
                segNameStart += LOAD_NAME_LEN;
            }

            // Extract SEGNAME.  May be fixed- or variable-width.
            string segName;

            if (newSeg.LabLen == 0)
            {
                // string preceded by length byte
                int segNameLen = data[offset + segNameStart];
                if (segNameStart + 1 + segNameLen > segLen)
                {
                    AddInfoMsg(msgs, offset, "var-width SEGNAME ran off end of segment (len=" +
                               segNameLen + ", segLen=" + segLen + ")");
                    return(ParseResult.Failure);
                }
                segName = Encoding.ASCII.GetString(data, offset + segNameStart + 1, segNameLen);
            }
            else
            {
                // fixed-width string
                if (segNameStart + newSeg.LabLen > segLen)
                {
                    AddInfoMsg(msgs, offset, "fixed-width SEGNAME ran off end of segment (LABLEN=" +
                               newSeg.LabLen + ", segLen=" + segLen + ")");
                    return(ParseResult.Failure);
                }
                segName = ExtractString(data, offset + segNameStart, newSeg.LabLen);
            }

            //AddInfoMsg(msgs, offset, "GOT LOADNAME='" + loadName + "' SEGNAME='" + segName + "'");

            newSeg.LoadName = loadName;
            newSeg.SegName  = segName;

            //
            // Populate the "raw data" table.  We add the fields shown in the specification in
            // the order in which they appear.
            //

            if (newSeg.Version == SegmentVersion.v0_0 ||
                (newSeg.Version == SegmentVersion.v1_0 && !parseAsLibrary))
            {
                newSeg.AddRaw("BLKCNT", blkByteCnt, 4, "blocks");
            }
            else
            {
                newSeg.AddRaw("BYTECNT", blkByteCnt, 4, "bytes");
            }
            newSeg.AddRaw("RESSPC", newSeg.ResSpc, 4, string.Empty);
            newSeg.AddRaw("LENGTH", newSeg.Length, 4, string.Empty);
            if (newSeg.Version <= SegmentVersion.v1_0)
            {
                string attrStr = AttrsToString(newSeg.Attrs);
                if (!string.IsNullOrEmpty(attrStr))
                {
                    attrStr = " -" + attrStr;
                }
                newSeg.AddRaw("KIND", data[offset + 0x0c], 1,
                              KindToString(newSeg.Kind) + attrStr);
            }
            else
            {
                newSeg.AddRaw("undefined", data[offset + 0x0c], 1, string.Empty);
            }
            newSeg.AddRaw("LABLEN", newSeg.LabLen, 1,
                          (newSeg.LabLen == 0 ? "variable length" : "fixed length"));
            newSeg.AddRaw("NUMLEN", numLen, 1, "must be 4");
            newSeg.AddRaw("VERSION", data[offset + 0x0f], 1, VersionToString(newSeg.Version));
            newSeg.AddRaw("BANKSIZE", newSeg.BankSize, 4, string.Empty);
            if (newSeg.Version >= SegmentVersion.v2_0)
            {
                string attrStr = AttrsToString(newSeg.Attrs);
                if (!string.IsNullOrEmpty(attrStr))
                {
                    attrStr = " -" + attrStr;
                }
                newSeg.AddRaw("KIND", RawData.GetWord(data, offset + 0x14, 2, false), 2,
                              KindToString(newSeg.Kind) + attrStr);
                newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x16, 2, false), 2,
                              string.Empty);
            }
            else
            {
                newSeg.AddRaw("undefined", RawData.GetWord(data, offset + 0x14, 4, false), 4,
                              string.Empty);
            }
            newSeg.AddRaw("ORG", newSeg.Org, 4, (newSeg.Org != 0 ? "" : "relocatable"));
            // alignment is rounded up to page/bank
            string alignStr;

            if (newSeg.Align == 0)
            {
                alignStr = "no alignment";
            }
            else if (newSeg.Align <= 0x0100)
            {
                alignStr = "align to page";
            }
            else
            {
                alignStr = "align to bank";
            }
            newSeg.AddRaw("ALIGN", newSeg.Align, 4, alignStr);
            newSeg.AddRaw("NUMSEX", numSex, 1, "must be 0");
            if (newSeg.Version == SegmentVersion.v1_0)
            {
                newSeg.AddRaw("LCBANK", newSeg.LcBank, 1, string.Empty);
            }
            else
            {
                newSeg.AddRaw("undefined", data[offset + 0x21], 1, string.Empty);
            }
            if (newSeg.Version >= SegmentVersion.v1_0)
            {
                newSeg.AddRaw("SEGNUM", newSeg.SegNum, 2, string.Empty);
                newSeg.AddRaw("ENTRY", newSeg.Entry, 4, string.Empty);
                newSeg.AddRaw("DISPNAME", dispName, 2, string.Empty);
                newSeg.AddRaw("DISPDATA", newSeg.DispData, 2, string.Empty);
                if (newSeg.Version >= SegmentVersion.v2_1)
                {
                    newSeg.AddRaw("TEMPORG", newSeg.TempOrg, 4, string.Empty);
                }
                newSeg.AddRaw("LOADNAME", loadName, 10, string.Empty);
            }
            newSeg.AddRaw("SEGNAME", segName, 0, string.Empty);

            segResult = newSeg;
            return(ParseResult.Success);
        }