On the design of the Delphi TStream classes – why aren’t they decomposed better?
Posted by jpluimers on 2017/05/16
Ever since I started using Delphi more than 2 decades ago, I wondered about the design of the TStream classes, especially as “stream16.zip” by Duncan Murdoch from the DOS Turbo Pascal era (which I copied in the below gist) showed how to perform composition using streams.
Even though by now there is TStreamReader/TStreamWriter allowing some level of composition, it always bugged me that large parts of the resource handling and component (de)serialisation is in it (centred around ReadComponentRes/WriteComponentRes in stead of fully being in TFiler/TReader/TWriter or even further decomposed), that you cannot interrogate capabilities (like supporting seeking, length, directionality and such) and that a lot of overloads (for instance Read/ReadBuffer/ReadBufferData/Write/WriteBuffer/WriteBufferData) are still based on signed or 32-bit types (though it has improved back in the early days they were even signed 16-bit types).
I’m not the only one who wonders about this: Faster FileStream with TBufferedFileStream • DelphiABall mentioned a new Berlin piece the TStream hierarchy and – being in the field a lot longer – Stefan Glienke rightly asked why the buffering isn’t done with the decorator pattern like JclStreams does, and Asbjørn Heid chimed in with a very dense version of more gripes.
Even TZCompressionStream/TZDecompressionStream (though relatively new) aren’t doing composition really well (by not abstracting the compression/decompression from the write-only/read-only behaviour).
Now that all key players from the early TStream design day and age have left the core Delphi R&D team, maybe one of them can step in and explain why.
–jeroen
via: [WayBack] I was wondering if the TBufferedFileStream (see https://delphiaball.co.uk/2016/04/29/faster-filestream-tbufferedfilestream/) would not have been implemented using the decorator pattern… – Stefan Glienke – Google+ -> https://github.com/project-jedi/jcl/blob/master/jcl/source/common/JclStreams.pas#L207
TJclBufferedStream = class(TJclStreamDecorator)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| program Compress; | |
| { Program to demonstrate use of TLZWFilter } | |
| {$i stdefine.inc} | |
| uses | |
| {$ifdef windows} wincrt, {$endif} | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| streams; | |
| procedure SyntaxExit(s:string); | |
| begin | |
| writeln; | |
| writeln(s); | |
| writeln; | |
| writeln('Usage: COMPRESS Sourcefile Destfile [/X]'); | |
| writeln(' will compress the source file to the destination'); | |
| writeln(' file, or if /X flag is used, will expand source to destination.'); | |
| halt(99); | |
| end; | |
| var | |
| Source : PStream; { We don't know in advance which will be compressed } | |
| Dest : PStream; | |
| filename : string; | |
| begin | |
| Case ParamCount of | |
| 2 : begin | |
| {$ifdef windows} | |
| Filename := Paramstr(1); | |
| Filename[length(filename)+1] := #0; | |
| Source := New(PBufStream, init(@filename[1], stOpenRead, 2048)); | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| Dest := New(PLZWFilter, init(New(PBufStream, | |
| init(@filename[1], | |
| stCreate, 2048)), | |
| stOpenWrite)); | |
| {$else} | |
| Source := New(PBufStream, init(Paramstr(1), stOpenRead, 2048)); | |
| Dest := New(PLZWFilter, init(New(PBufStream, | |
| init(Paramstr(2), | |
| stCreate, 2048)), | |
| stOpenWrite)); | |
| {$endif windows} | |
| Write('Compressing ',Paramstr(1),' (',Source^.GetSize, | |
| ' bytes) to ',Paramstr(2)); | |
| end; | |
| 3 : begin | |
| if (Paramstr(3) <> '/X') and (Paramstr(3) <> '/x') then | |
| SyntaxExit('Unrecognized option '+Paramstr(3)); | |
| {$ifdef windows} | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| Source := New(PLZWFilter, init(New(PBufStream, | |
| init(@filename[1], | |
| stOpenRead, 2048)), | |
| stOpenRead)); | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| Dest := New(PBufStream, init(@filename[1], stCreate, 2048)); | |
| {$else} | |
| Source := New(PLZWFilter, init(New(PBufStream, | |
| init(Paramstr(1), | |
| stOpenRead, 2048)), | |
| stOpenRead)); | |
| Dest := New(PBufStream, init(Paramstr(2), stCreate, 2048)); | |
| {$endif windows} | |
| Write('Expanding ',Paramstr(1),' (', | |
| PLZWFilter(Source)^.Base^.GetSize,' bytes) to ', | |
| Paramstr(2)); | |
| end; | |
| else | |
| SyntaxExit('Two or three parameters required.'); | |
| end; | |
| if (Source = nil) or (Source^.status <> stOk) then | |
| SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
| if (Dest = nil) or (Dest^.status <> stOk) then | |
| SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
| FastCopy(Source^,Dest^, Source^.GetSize); | |
| if Dest^.status <> stOK then | |
| SyntaxExit('File error during compression/expansion.'); | |
| Case ParamCount of | |
| 2 : begin | |
| Dest^.Flush; | |
| Writeln(' (',PLZWFilter(Dest)^.Base^.GetSize,' bytes).'); | |
| end; | |
| 3 : Writeln(' (',Dest^.GetSize,' bytes).'); | |
| end; | |
| Dispose(Source, done); | |
| Dispose(Dest, done); | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| IDEAL | |
| ; This CRC-16 routine and tables were converted from C code discovered | |
| ; in rzsz.arc by Chuck Forsberg. The comments there are: | |
| ; | |
| ; crctab calculated by Mark G. Mendel, Network Systems Corporation | |
| ; | |
| ; updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. | |
| ; NOTE: First srgument must be in range 0 to 255. | |
| ; Second argument is referenced twice. | |
| ; | |
| ; Programmers may incorporate any or all code into their programs, | |
| ; giving proper credit within the source. Publication of the | |
| ; source routines is permitted so long as proper credit is given | |
| ; to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, | |
| ; Omen Technology. | |
| ; | |
| ; <End of rzsz comments> | |
| ; | |
| ; The C macro is: | |
| ; | |
| ; #define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp) | |
| ; | |
| ; This routine appears to compute the 16 bit CRC used by XModem and its | |
| ; derivatives. For transmission, the CRC value should start with 0 and, | |
| ; after the block crc is computed, the value should be updated with two | |
| ; nulls and transmitted high-order byte first. The computed CRC value | |
| ; on the received block + 2 byte CRC should be zero. | |
| c:\dosauto | |
| ; | |
| ; This TASM conversion done by: | |
| ; | |
| ; Edwin T. Floyd [76067h747] | |
| ; #9 Adams Park Ct. | |
| ; Columbush GA 31909 | |
| ; 404-576-3305 (work) | |
| ; 404-322-0076 (home) | |
| ; | |
| ; Borland's Turbo Assembler – TASM is required to assemble this program. | |
| ; | |
| SEGMENT code BYTE PUBLIC | |
| ASSUME cs:code | |
| ; 0 | |
| crctab dw 00000h, 01021h, 02042h, 03063h, 04084h, 050a5h, 060c6h, 070e7h | |
| dw 08108h, 09129h, 0a14ah, 0b16bh, 0c18ch, 0d1adh, 0e1ceh, 0f1efh | |
| ; 1 | |
| dw 01231h, 00210h, 03273h, 02252h, 052b5h, 04294h, 072f7h, 062d6h | |
| dw 09339h, 08318h, 0b37bh, 0a35ah, 0d3bdh, 0c39ch, 0f3ffh, 0e3deh | |
| ; 2 | |
| dw 02462h, 03443h, 00420h, 01401h, 064e6h, 074c7h, 044a4h, 05485h | |
| dw 0a56ah, 0b54bh, 08528h, 09509h, 0e5eeh, 0f5cfh, 0c5ach, 0d58dh | |
| ; 3 | |
| dw 03653h, 02672h, 01611h, 00630h, 076d7h, 066f6h, 05695h, 046b4h | |
| dw 0b75bh, 0a77ah, 09719h, 08738h, 0f7dfh, 0e7feh, 0d79dh, 0c7bch | |
| ; 4 | |
| dw 048c4h, 058e5h, 06886h, 078a7h, 00840h, 01861h, 02802h, 03823h | |
| dw 0c9cch, 0d9edh, 0e98eh, 0f9afh, 08948h, 09969h, 0a90ah, 0b92bh | |
| ; 5 | |
| dw 05af5h, 04ad4h, 07ab7h, 06a96h, 01a71h, 00a50h, 03a33h, 02a12h | |
| dw 0dbfdh, 0cbdch, 0fbbfh, 0eb9eh, 09b79h, 08b58h, 0bb3bh, 0ab1ah | |
| ; 6 | |
| dw 06ca6h, 07c87h, 04ce4h, 05cc5h, 02c22h, 03c03h, 00c60h, 01c41h | |
| dw 0edaeh, 0fd8fh, 0cdech, 0ddcdh, 0ad2ah, 0bd0bh, 08d68h, 09d49h | |
| ; 7 | |
| dw 07e97h, 06eb6h, 05ed5h, 04ef4h, 03e13h, 02e32h, 01e51h, 00e70h | |
| dw 0ff9fh, 0efbeh, 0dfddh, 0cffch, 0bf1bh, 0af3ah, 09f59h, 08f78h | |
| ; 8 | |
| dw 09188h, 081a9h, 0b1cah, 0a1ebh, 0d10ch, 0c12dh, 0f14eh, 0e16fh | |
| dw 01080h, 000a1h, 030c2h, 020e3h, 05004h, 04025h, 07046h, 06067h | |
| ; 9 | |
| dw 083b9h, 09398h, 0a3fbh, 0b3dah, 0c33dh, 0d31ch, 0e37fh, 0f35eh | |
| dw 002b1h, 01290h, 022f3h, 032d2h, 04235h, 05214h, 06277h, 07256h | |
| ; A | |
| dw 0b5eah, 0a5cbh, 095a8h, 08589h, 0f56eh, 0e54fh, 0d52ch, 0c50dh | |
| dw 034e2h, 024c3h, 014a0h, 00481h, 07466h, 06447h, 05424h, 04405h | |
| ; B | |
| dw 0a7dbh, 0b7fah, 08799h, 097b8h, 0e75fh, 0f77eh, 0c71dh, 0d73ch | |
| dw 026d3h, 036f2h, 00691h, 016b0h, 06657h, 07676h, 04615h, 05634h | |
| ; C | |
| dw 0d94ch, 0c96dh, 0f90eh, 0e92fh, 099c8h, 089e9h, 0b98ah, 0a9abh | |
| dw 05844h, 04865h, 07806h, 06827h, 018c0h, 008e1h, 03882h, 028a3h | |
| ; D | |
| dw 0cb7dh, 0db5ch, 0eb3fh, 0fb1eh, 08bf9h, 09bd8h, 0abbbh, 0bb9ah | |
| dw 04a75h, 05a54h, 06a37h, 07a16h, 00af1h, 01ad0h, 02ab3h, 03a92h | |
| ; E | |
| dw 0fd2eh, 0ed0fh, 0dd6ch, 0cd4dh, 0bdaah, 0ad8bh, 09de8h, 08dc9h | |
| dw 07c26h, 06c07h, 05c64h, 04c45h, 03ca2h, 02c83h, 01ce0h, 00cc1h | |
| ; F | |
| dw 0ef1fh, 0ff3eh, 0cf5dh, 0df7ch, 0af9bh, 0bfbah, 08fd9h, 09ff8h | |
| dw 06e17h, 07e36h, 04e55h, 05e74h, 02e93h, 03eb2h, 00ed1h, 01ef0h | |
| MODEL TPASCAL | |
| PUBLIC UpdateCRC16 | |
| PROC UpdateCRC16 FAR initcrc:WORD,inbuf:DWORD,inlen:WORD | |
| ; UpdateCRC16 takes an initial CRC value and updates it with inlen bytes from | |
| ; inbuf. The updated CRC is returned in AX. The Pascal declaration is: | |
| ; Function UpdateCRC16(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| ; Stomps registers: AX,BX,CX,SI | |
| push ds | |
| lds si,[inbuf] ; ds:si := ^inbuf | |
| mov ax,[initcrc] ; ax := initcrc | |
| mov cx,[inlen] ; cx := inlen | |
| or cx,cx | |
| jz @@done | |
| @@loop: | |
| xor bh,bh | |
| mov bl,ah | |
| mov ah,al | |
| lodsb | |
| shl bx,1 | |
| xor ax,[crctab+bx] | |
| loop @@loop | |
| @@done: | |
| pop ds | |
| ret | |
| ENDP | |
| ENDS | |
| END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| IDEAL | |
| ; This CRC-32 routine and tables were converted from code discovered | |
| ; in the DEZIP.PAS V2.0 by R. P. Byrne. The comments there are: | |
| ; | |
| ; Converted to Turbo Pascal ™ V4.0 March, 1988 by J.R.Louvau | |
| ; COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or | |
| ; code or tables extracted from it, as desired without restriction. | |
| ; | |
| ; First, the polynomial itself and its table of feedback terms. The | |
| ; polynomial is | |
| ; X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 | |
| ; | |
| ; Note that we take it "backwards" and put the highest-order term in | |
| ; the lowest-order bit. The X^32 term is "implied"; the LSB is the | |
| ; X^31 term, etc. The X^0 term (usually shown as "+1") results in | |
| ; the MSB being 1. | |
| ; | |
| ; Note that the usual hardware shift register implementation, which | |
| ; is what we're using (we're merely optimizing it by doing eight-bit | |
| ; chunks at a time) shifts bits into the lowest-order term. In our | |
| ; implementation, that means shifting towards the right. Why do we | |
| ; do it this way? Because the calculated CRC must be transmitted in | |
| ; order from highest-order term to lowest-order term. UARTs transmit | |
| ; characters in order from LSB to MSB. By storing the CRC this way, | |
| ; we hand it to the UART in the order low-byte to high-byte; the UART | |
| ; sends each low-bit to high-bit; and the result is transmission bit | |
| ; by bit from highest- to lowest-order term without requiring any bit | |
| ; shuffling on our part. Reception works similarly. | |
| ; | |
| ; The feedback terms table consists of 256, 32-bit entries. Notes: | |
| ; | |
| ; The table can be generated at runtime if desired; code to do so | |
| ; is shown later. It might not be obvious, but the feedback | |
| ; terms simply represent the results of eight shift/xor opera- | |
| ; tions for all combinations of data and CRC register values. | |
| ; | |
| ; The values must be right-shifted by eight bits by the "updcrc" | |
| ; logic; the shift must be unsigned (bring in zeroes). On some | |
| ; hardware you could probably optimize the shift in assembler by | |
| ; using byte-swap instructions. | |
| ; polynomial $edb88320 | |
| ; | |
| ; <End of Pascal version comments> | |
| ; | |
| ; The Pascal logic is: | |
| ; | |
| ; Function UpdC32(Octet: Byte; Crc: LongInt) : LongInt; | |
| ; Begin | |
| ; | |
| ; UpdC32 := CRC_32_TAB[Byte(Crc XOR LongInt(Octet))] XOR ((Crc SHR 8) | |
| ; AND $00FFFFFF); | |
| ; | |
| ; End {UpdC32}; | |
| ; | |
| ; This routine computes the 32 bit CRC used by PKZIP and its derivatives, | |
| ; and by Chuck Forsberg's "ZMODEM" protocol. The block CRC computation | |
| ; should start with high-values (0ffffffffh), and finish by inverting all | |
| ; bits. | |
| ; | |
| ; This TASM conversion done by: | |
| ; | |
| ; Edwin T. Floyd [76067,747] | |
| ; #9 Adams Park Ct. | |
| ; Columbus, GA 31909 | |
| ; 404-576-3305 (work) | |
| ; 404-322-0076 (home) | |
| ; | |
| ; Borland's Turbo Assembler – TASM is required to assemble this program. | |
| ; | |
| SEGMENT code BYTE PUBLIC | |
| ASSUME cs:code | |
| ; 0 | |
| crc32tab dd 000000000h, 077073096h, 0ee0e612ch, 0990951bah | |
| dd 0076dc419h, 0706af48fh, 0e963a535h, 09e6495a3h | |
| dd 00edb8832h, 079dcb8a4h, 0e0d5e91eh, 097d2d988h | |
| dd 009b64c2bh, 07eb17cbdh, 0e7b82d07h, 090bf1d91h | |
| ; 1 | |
| dd 01db71064h, 06ab020f2h, 0f3b97148h, 084be41deh | |
| dd 01adad47dh, 06ddde4ebh, 0f4d4b551h, 083d385c7h | |
| dd 0136c9856h, 0646ba8c0h, 0fd62f97ah, 08a65c9ech | |
| dd 014015c4fh, 063066cd9h, 0fa0f3d63h, 08d080df5h | |
| ; 2 | |
| dd 03b6e20c8h, 04c69105eh, 0d56041e4h, 0a2677172h | |
| dd 03c03e4d1h, 04b04d447h, 0d20d85fdh, 0a50ab56bh | |
| dd 035b5a8fah, 042b2986ch, 0dbbbc9d6h, 0acbcf940h | |
| dd 032d86ce3h, 045df5c75h, 0dcd60dcfh, 0abd13d59h | |
| ; 3 | |
| dd 026d930ach, 051de003ah, 0c8d75180h, 0bfd06116h | |
| dd 021b4f4b5h, 056b3c423h, 0cfba9599h, 0b8bda50fh | |
| dd 02802b89eh, 05f058808h, 0c60cd9b2h, 0b10be924h | |
| dd 02f6f7c87h, 058684c11h, 0c1611dabh, 0b6662d3dh | |
| ; 4 | |
| dd 076dc4190h, 001db7106h, 098d220bch, 0efd5102ah | |
| dd 071b18589h, 006b6b51fh, 09fbfe4a5h, 0e8b8d433h | |
| dd 07807c9a2h, 00f00f934h, 09609a88eh, 0e10e9818h | |
| dd 07f6a0dbbh, 0086d3d2dh, 091646c97h, 0e6635c01h | |
| ; 5 | |
| dd 06b6b51f4h, 01c6c6162h, 0856530d8h, 0f262004eh | |
| dd 06c0695edh, 01b01a57bh, 08208f4c1h, 0f50fc457h | |
| dd 065b0d9c6h, 012b7e950h, 08bbeb8eah, 0fcb9887ch | |
| dd 062dd1ddfh, 015da2d49h, 08cd37cf3h, 0fbd44c65h | |
| ; 6 | |
| dd 04db26158h, 03ab551ceh, 0a3bc0074h, 0d4bb30e2h | |
| dd 04adfa541h, 03dd895d7h, 0a4d1c46dh, 0d3d6f4fbh | |
| dd 04369e96ah, 0346ed9fch, 0ad678846h, 0da60b8d0h | |
| dd 044042d73h, 033031de5h, 0aa0a4c5fh, 0dd0d7cc9h | |
| ; 7 | |
| dd 05005713ch, 0270241aah, 0be0b1010h, 0c90c2086h | |
| dd 05768b525h, 0206f85b3h, 0b966d409h, 0ce61e49fh | |
| dd 05edef90eh, 029d9c998h, 0b0d09822h, 0c7d7a8b4h | |
| dd 059b33d17h, 02eb40d81h, 0b7bd5c3bh, 0c0ba6cadh | |
| ; 8 | |
| dd 0edb88320h, 09abfb3b6h, 003b6e20ch, 074b1d29ah | |
| dd 0ead54739h, 09dd277afh, 004db2615h, 073dc1683h | |
| dd 0e3630b12h, 094643b84h, 00d6d6a3eh, 07a6a5aa8h | |
| dd 0e40ecf0bh, 09309ff9dh, 00a00ae27h, 07d079eb1h | |
| ; 9 | |
| dd 0f00f9344h, 08708a3d2h, 01e01f268h, 06906c2feh | |
| dd 0f762575dh, 0806567cbh, 0196c3671h, 06e6b06e7h | |
| dd 0fed41b76h, 089d32be0h, 010da7a5ah, 067dd4acch | |
| dd 0f9b9df6fh, 08ebeeff9h, 017b7be43h, 060b08ed5h | |
| ; A | |
| dd 0d6d6a3e8h, 0a1d1937eh, 038d8c2c4h, 04fdff252h | |
| dd 0d1bb67f1h, 0a6bc5767h, 03fb506ddh, 048b2364bh | |
| dd 0d80d2bdah, 0af0a1b4ch, 036034af6h, 041047a60h | |
| dd 0df60efc3h, 0a867df55h, 0316e8eefh, 04669be79h | |
| ; B | |
| dd 0cb61b38ch, 0bc66831ah, 0256fd2a0h, 05268e236h | |
| dd 0cc0c7795h, 0bb0b4703h, 0220216b9h, 05505262fh | |
| dd 0c5ba3bbeh, 0b2bd0b28h, 02bb45a92h, 05cb36a04h | |
| dd 0c2d7ffa7h, 0b5d0cf31h, 02cd99e8bh, 05bdeae1dh | |
| ; C | |
| dd 09b64c2b0h, 0ec63f226h, 0756aa39ch, 0026d930ah | |
| dd 09c0906a9h, 0eb0e363fh, 072076785h, 005005713h | |
| dd 095bf4a82h, 0e2b87a14h, 07bb12baeh, 00cb61b38h | |
| dd 092d28e9bh, 0e5d5be0dh, 07cdcefb7h, 00bdbdf21h | |
| ; D | |
| dd 086d3d2d4h, 0f1d4e242h, 068ddb3f8h, 01fda836eh | |
| dd 081be16cdh, 0f6b9265bh, 06fb077e1h, 018b74777h | |
| dd 088085ae6h, 0ff0f6a70h, 066063bcah, 011010b5ch | |
| dd 08f659effh, 0f862ae69h, 0616bffd3h, 0166ccf45h | |
| ; E | |
| dd 0a00ae278h, 0d70dd2eeh, 04e048354h, 03903b3c2h | |
| dd 0a7672661h, 0d06016f7h, 04969474dh, 03e6e77dbh | |
| dd 0aed16a4ah, 0d9d65adch, 040df0b66h, 037d83bf0h | |
| dd 0a9bcae53h, 0debb9ec5h, 047b2cf7fh, 030b5ffe9h | |
| ; F | |
| dd 0bdbdf21ch, 0cabac28ah, 053b39330h, 024b4a3a6h | |
| dd 0bad03605h, 0cdd70693h, 054de5729h, 023d967bfh | |
| dd 0b3667a2eh, 0c4614ab8h, 05d681b02h, 02a6f2b94h | |
| dd 0b40bbe37h, 0c30c8ea1h, 05a05df1bh, 02d02ef8dh | |
| MODEL TPASCAL | |
| PUBLIC UpdateCRC32 | |
| PROC UpdateCRC32 FAR initcrc:DWORD,inbuf:DWORD,inlen:WORD | |
| ; UpdateCRC32 takes an initial CRC value and updates it with inlen bytes from | |
| ; inbuf. The updated CRC is returned in DX:AX. The Pascal declaration is: | |
| ; Function UpdateCRC32(InitCRC : LongInt; Var InBuf; InLen : Word) : LongInt; | |
| ; Stomps registers: AX,BX,CX,DX,ES,SI | |
| push ds | |
| lds si,[inbuf] ; ds:si := ^inbuf | |
| les ax,[initcrc] ; dx:ax := initcrc | |
| mov dx,es | |
| mov cx,[inlen] ; cx := inlen | |
| or cx,cx | |
| jz @@done | |
| @@loop: | |
| xor bh,bh | |
| mov bl,al | |
| lodsb | |
| xor bl,al | |
| mov al,ah | |
| mov ah,dl | |
| mov dl,dh | |
| xor dh,dh | |
| shl bx,1 | |
| shl bx,1 | |
| les bx,[crc32tab+bx] | |
| xor ax,bx | |
| mov bx,es | |
| xor dx,bx | |
| loop @@loop | |
| @@done: | |
| pop ds | |
| ret | |
| ENDP | |
| ENDS | |
| END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| IDEAL | |
| ; This CRC-16 routine and tables were converted from pascal code discovered | |
| ; in DEARC.PAS (author unknown). The result matches the 16 bit CRC computed | |
| ; by SEA's "ARC" utility. This CRC routine is contributed to the public | |
| ; domain. | |
| ; | |
| ; The Pascal logic is: | |
| ; | |
| ; crc := ((crc shr 8) and $00FF) xor crctab[(crc xor c) and $00FF]; | |
| ; | |
| ; This TASM conversion done by: | |
| ; | |
| ; Edwin T. Floyd [76067h747] | |
| ; #9 Adams Park Ct. | |
| ; Columbus, GA 31909 | |
| ; 404-576-3305 (work) | |
| ; 404-322-0076 (home) | |
| ; | |
| ; Borland's Turbo Assembler – TASM is required to assemble this program. | |
| ; | |
| SEGMENT code BYTE PUBLIC | |
| ASSUME cs:code | |
| ; 0 | |
| crctab dw 00000h, 0C0C1h, 0C181h, 00140h, 0C301h, 003C0h, 00280h, 0C241h | |
| dw 0C601h, 006C0h, 00780h, 0C741h, 00500h, 0C5C1h, 0C481h, 00440h | |
| ; 1 | |
| dw 0CC01h, 00CC0h, 00D80h, 0CD41h, 00F00h, 0CFC1h, 0CE81h, 00E40h | |
| dw 00A00h, 0CAC1h, 0CB81h, 00B40h, 0C901h, 009C0h, 00880h, 0C841h | |
| ; 2 | |
| dw 0D801h, 018C0h, 01980h, 0D941h, 01B00h, 0DBC1h, 0DA81h, 01A40h | |
| dw 01E00h, 0DEC1h, 0DF81h, 01F40h, 0DD01h, 01DC0h, 01C80h, 0DC41h | |
| ; 3 | |
| dw 01400h, 0D4C1h, 0D581h, 01540h, 0D701h, 017C0h, 01680h, 0D641h | |
| dw 0D201h, 012C0h, 01380h, 0D341h, 01100h, 0D1C1h, 0D081h, 01040h | |
| ; 4 | |
| dw 0F001h, 030C0h, 03180h, 0F141h, 03300h, 0F3C1h, 0F281h, 03240h | |
| dw 03600h, 0F6C1h, 0F781h, 03740h, 0F501h, 035C0h, 03480h, 0F441h | |
| ; 5 | |
| dw 03C00h, 0FCC1h, 0FD81h, 03D40h, 0FF01h, 03FC0h, 03E80h, 0FE41h | |
| dw 0FA01h, 03AC0h, 03B80h, 0FB41h, 03900h, 0F9C1h, 0F881h, 03840h | |
| ; 6 | |
| dw 02800h, 0E8C1h, 0E981h, 02940h, 0EB01h, 02BC0h, 02A80h, 0EA41h | |
| dw 0EE01h, 02EC0h, 02F80h, 0EF41h, 02D00h, 0EDC1h, 0EC81h, 02C40h | |
| ; 7 | |
| dw 0E401h, 024C0h, 02580h, 0E541h, 02700h, 0E7C1h, 0E681h, 02640h | |
| dw 02200h, 0E2C1h, 0E381h, 02340h, 0E101h, 021C0h, 02080h, 0E041h | |
| ; 8 | |
| dw 0A001h, 060C0h, 06180h, 0A141h, 06300h, 0A3C1h, 0A281h, 06240h | |
| dw 06600h, 0A6C1h, 0A781h, 06740h, 0A501h, 065C0h, 06480h, 0A441h | |
| ; 9 | |
| dw 06C00h, 0ACC1h, 0AD81h, 06D40h, 0AF01h, 06FC0h, 06E80h, 0AE41h | |
| dw 0AA01h, 06AC0h, 06B80h, 0AB41h, 06900h, 0A9C1h, 0A881h, 06840h | |
| ; A | |
| dw 07800h, 0B8C1h, 0B981h, 07940h, 0BB01h, 07BC0h, 07A80h, 0BA41h | |
| dw 0BE01h, 07EC0h, 07F80h, 0BF41h, 07D00h, 0BDC1h, 0BC81h, 07C40h | |
| ; B | |
| dw 0B401h, 074C0h, 07580h, 0B541h, 07700h, 0B7C1h, 0B681h, 07640h | |
| dw 07200h, 0B2C1h, 0B381h, 07340h, 0B101h, 071C0h, 07080h, 0B041h | |
| ; C | |
| dw 05000h, 090C1h, 09181h, 05140h, 09301h, 053C0h, 05280h, 09241h | |
| dw 09601h, 056C0h, 05780h, 09741h, 05500h, 095C1h, 09481h, 05440h | |
| ; D | |
| dw 09C01h, 05CC0h, 05D80h, 09D41h, 05F00h, 09FC1h, 09E81h, 05E40h | |
| dw 05A00h, 09AC1h, 09B81h, 05B40h, 09901h, 059C0h, 05880h, 09841h | |
| ; E | |
| dw 08801h, 048C0h, 04980h, 08941h, 04B00h, 08BC1h, 08A81h, 04A40h | |
| dw 04E00h, 08EC1h, 08F81h, 04F40h, 08D01h, 04DC0h, 04C80h, 08C41h | |
| ; F | |
| dw 04400h, 084C1h, 08581h, 04540h, 08701h, 047C0h, 04680h, 08641h | |
| dw 08201h, 042C0h, 04380h, 08341h, 04100h, 081C1h, 08081h, 04040h | |
| MODEL TPASCAL | |
| PUBLIC UpdateCRCArc | |
| PROC UpdateCRCArc FAR initcrc:WORD,inbuf:DWORD,inlen:WORD | |
| ; UpdateCRCArc takes an initial CRC value and updates it with inlen bytes from | |
| ; inbuf. The updated CRC is returned in AX. The Pascal declaration is: | |
| ; Function UpdateCRCArc(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| ; Stomps registers: AX,BX,CX,SI | |
| push ds | |
| lds si,[inbuf] ; ds:si := ^inbuf | |
| mov ax,[initcrc] ; ax := initcrc | |
| mov cx,[inlen] ; cx := inlen | |
| or cx,cx | |
| jz @@done | |
| @@loop: | |
| xor bh,bh | |
| mov bl,al | |
| lodsb | |
| xor bl,al | |
| shl bx,1 | |
| mov bx,[crctab+bx] | |
| xor bl,ah | |
| mov ax,bx | |
| loop @@loop | |
| @@done: | |
| pop ds | |
| ret | |
| ENDP | |
| ENDS | |
| END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| program Encrypt; | |
| { Program to demonstrate use of TEncryptFilter } | |
| {$i StDefine.inc} | |
| uses | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| {$ifdef windows} wincrt, {$endif} | |
| streams; | |
| procedure SyntaxExit(s:string); | |
| begin | |
| writeln; | |
| writeln(s); | |
| writeln; | |
| writeln('Usage: ENCRYPT Sourcefile Destfile'); | |
| writeln(' will encrypt sourcefile using key $12345678.'); | |
| writeln(' Run ENCRYPT on the encrypted file to decrypt it.'); | |
| halt(99); | |
| end; | |
| var | |
| Source : PBufStream; | |
| Dest : PEncryptFilter; | |
| filename : string; | |
| begin | |
| if paramcount <> 2 then | |
| SyntaxExit('Two parameters required.'); | |
| { Open the source file with a buffer size of 2048. } | |
| {$ifdef windows} | |
| Filename := Paramstr(1); | |
| Filename[length(filename)+1] := #0; | |
| New(Source, Init( @filename[1], stOpenRead, 2048) ); | |
| {$else} | |
| New(Source, Init( Paramstr(1), stOpenRead, 2048) ); | |
| {$endif windows} | |
| if (Source = nil) or (Source^.status <> stOk) then | |
| SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
| { Open the destination file with a buffer size of 2048, and insert it | |
| into the encrypting filter. } | |
| {$ifdef windows} | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| New(Dest, Init($12345678, New(PBufStream, | |
| Init( @filename[1], stCreate, 2048)))); | |
| {$else} | |
| New(Dest, Init($12345678, New(PBufStream, | |
| Init( Paramstr(2), stCreate, 2048)))); | |
| {$endif windows} | |
| if (Dest = nil) or (Dest^.status <> stOk) then | |
| SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
| { Encrypt the source file by copying it to the filter.} | |
| Write('Encrypting ',Paramstr(1),' to ',Paramstr(2),'…'); | |
| FastCopy(Source^, Dest^, Source^.GetSize); | |
| if Dest^.status <> stOK then | |
| SyntaxExit('File error during encryption.'); | |
| { Dispose of stream variables to close the files.} | |
| Dispose(Source, done); | |
| Dispose(Dest, done); | |
| Writeln('Done.'); | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| Program HuffComp; | |
| { Simple compression program using Huffman compression. Much like | |
| COMPRESS.PAS. } | |
| {$i StDefine.inc} | |
| uses | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| {$ifdef windows} wincrt, {$endif} | |
| streams, huffman; | |
| procedure SyntaxExit(s:string); | |
| begin | |
| writeln; | |
| writeln(s); | |
| writeln; | |
| writeln('Usage: HUFFMAN Sourcefile Destfile [/X]'); | |
| writeln(' will compress the source file to the destination'); | |
| writeln(' file, or if /X flag is used, will expand source to destination.'); | |
| halt(99); | |
| end; | |
| var | |
| Source : PStream; { We don't know in advance which will be compressed } | |
| Dest : PStream; | |
| Fullsize:longint; | |
| Filename : string; | |
| begin | |
| Case ParamCount of | |
| 2 : begin | |
| {$ifdef windows} | |
| Filename := Paramstr(1); | |
| Filename[length(filename)+1] := #0; | |
| Source := New(PBufStream, init(@filename[1], stOpenRead, 2048)); | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| Dest := New(PHuffmanFilter, init(New(PBufStream, | |
| init(@filename[1], | |
| stCreate, 2048)))); | |
| {$else} | |
| Source := New(PBufStream, init(Paramstr(1), stOpenRead, 2048)); | |
| Dest := New(PHuffmanFilter, init(New(PBufStream, | |
| init(Paramstr(2), | |
| stCreate, 2048)))); | |
| {$endif windows} | |
| Write('Compressing ',Paramstr(1),' (',Source^.GetSize, | |
| ' bytes) to ',Paramstr(2)); | |
| { Count characters in source. } | |
| FullSize := Source^.GetSize; | |
| Dest^.Write(FullSize,sizeof(FullSize)); | |
| FastCopy(Source^,Dest^,Source^.GetSize); | |
| Source^.Seek(0); | |
| With PHuffmanFilter(Dest)^ do | |
| begin | |
| Seek(0); | |
| BuildCode; | |
| StoreCode; | |
| Learning := false; | |
| Write(Fullsize,sizeof(Fullsize)); | |
| end; | |
| end; | |
| 3 : begin | |
| if (Paramstr(3) <> '/X') and (Paramstr(3) <> '/x') then | |
| SyntaxExit('Unrecognized option '+Paramstr(3)); | |
| {$ifdef windows} | |
| Filename := Paramstr(1); | |
| Filename[length(filename)+1] := #0; | |
| Source := New(PHuffmanFilter, init(New(PBufStream, | |
| init(@filename[1], | |
| stOpenRead, 2048)))); | |
| Filename := Paramstr(2); | |
| Filename[length(filename)+1] := #0; | |
| Dest := New(PBufStream, init(@filename[1], stCreate, 2048)); | |
| {$else} | |
| Source := New(PHuffmanFilter, init(New(PBufStream, | |
| init(Paramstr(1), | |
| stOpenRead, 2048)))); | |
| Dest := New(PBufStream, init(Paramstr(2), stCreate, 2048)); | |
| {$endif} | |
| Write('Expanding ',Paramstr(1),' (', | |
| PHuffmanFilter(Source)^.Base^.GetSize,' bytes) to ', | |
| Paramstr(2)); | |
| with PHuffmanFilter(Source)^ do | |
| begin | |
| LoadCode; | |
| Learning := false; | |
| Read(Fullsize,Sizeof(Fullsize)); | |
| end; | |
| end; | |
| else | |
| SyntaxExit('Two or three parameters required.'); | |
| end; | |
| if (Source = nil) or (Source^.status <> stOk) then | |
| SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
| if (Dest = nil) or (Dest^.status <> stOk) then | |
| SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
| FastCopy(Source^, Dest^, FullSize); | |
| if Dest^.status <> stOK then | |
| SyntaxExit('File error during compression/expansion.'); | |
| Case ParamCount of | |
| 2 : begin | |
| Dest^.Flush; | |
| Writeln(' (',PHuffmanFilter(Dest)^.Base^.GetSize,' bytes).'); | |
| end; | |
| 3 : Writeln(' (',FullSize,' bytes).'); | |
| end; | |
| Dispose(Source, done); | |
| Dispose(Dest, done); | |
| end. | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| unit Huffman; { Copyright D.J. Murdoch, (1992) } | |
| { Defines a Huffman compression filter to illustrate use of the TBitFilter. } | |
| { The THuffmanFilter object defined in this file isn't optimized as much as | |
| I'd like, so I haven't put it into the main Streams unit. It's also a | |
| little rough – be careful if you use it. If you make any substantial | |
| improvements, I'd like to see them! – djm} | |
| interface | |
| {$i StDefine.inc} | |
| uses | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| streams; | |
| const | |
| MaxNode = 510; | |
| StoreSize = ((MaxNode-255)*18+7) div 8; { Bytes required to store the code | |
| table } | |
| type | |
| PHuffmanfilter = ^THuffmanfilter; | |
| THuffmanfilter = object(TBitfilter) | |
| { This object defines a Huffman encoder/decoder which encodes the 256 | |
| letter alphabet of bytes using variable length codes in the 2 letter | |
| alphabet of bits. } | |
| Size, { The size of the expanded stream. } | |
| Position : LongInt; { The current position in the expanded stream } | |
| Counts : array[0..MaxNode] of longint; { Counts uncompressed characters; | |
| second half used as workspace } | |
| Decoder : array[256..MaxNode,TBit] of integer; { Array holding decoder } | |
| EncodeStates : array[0..MaxNode] of integer; { The state change array } | |
| EncodeBits : array[0..MaxNode] of TBit; { The encoding bit for each | |
| state } | |
| Learning : boolean; { Signals whether writes are enabled, and whether | |
| to attempt to decode reads. } | |
| constructor init(ABase:PStream); | |
| { Inits the Counts to 0, but doesn't set up a code. Puts filter | |
| in "learning" mode. Before setting Learning to false, be sure to | |
| call LoadCode or BuildCode. } | |
| procedure LoadCode; | |
| { Reads an encoding from the base stream. } | |
| procedure StoreCode; | |
| { Writes an encoding to the base stream. } | |
| procedure BuildCode; | |
| { Builds the optimal encoding based on the values in the Counts array } | |
| procedure BuildEncoder(Verify:boolean); | |
| { Initializes the Encode arrays based on the Decoder array. Called | |
| automatically by LoadCode and BuildCode; use this routine only | |
| if you've loaded the Decoder in some other way. If Verify is true, | |
| it will check that the Decoder array is valid. } | |
| function CodeBits(b:byte):word; | |
| { Returns the number of bits that will be used in the current code | |
| to write b. } | |
| function PredictedSize:Longint; | |
| { Returns the predicted number of bytes to write the distribution of | |
| bytes given in Counts in the current encoding. } | |
| procedure read(var buf; count:word); virtual; | |
| procedure write(var buf; count:word); virtual; | |
| function getpos:longint; virtual; | |
| function getsize:longint; virtual; | |
| end; | |
| implementation | |
| constructor THuffmanFilter.Init(ABase:PStream); | |
| begin | |
| if not TFilter.Init(ABase) then | |
| fail; | |
| Size := 0; | |
| Position := 0; | |
| FillChar(counts,sizeof(counts),0); | |
| Learning := true; | |
| end; | |
| procedure THuffmanFilter.LoadCode; | |
| var | |
| i,code : integer; | |
| begin | |
| for i:=256 to MaxNode do | |
| begin | |
| ReadBits(code,9); | |
| Decoder[i,0] := code; { Should we confirm code<=MaxNode? } | |
| ReadBits(code,9); | |
| Decoder[i,1] := code; | |
| end; | |
| BuildEncoder(true); | |
| end; | |
| procedure THuffmanFilter.StoreCode; | |
| var | |
| i : integer; | |
| begin | |
| for i:=256 to MaxNode do | |
| begin | |
| WriteBits(Decoder[i,0],9); | |
| WriteBits(Decoder[i,1],9); | |
| end; | |
| end; | |
| procedure THuffmanFilter.BuildCode; | |
| var | |
| letters : array[byte] of integer; { The array of symbols } | |
| procedure Revsort; | |
| { Procedure to do a Quicksort on the array of letters, | |
| to put Counts[letters[i]] into decreasing order. | |
| Ties are broken by the letter order. | |
| Based on Quicksort as given in Steal This Code, by F.D. Boswell, Watcom 1986. | |
| } | |
| procedure quick(first,last : integer); | |
| var | |
| pivot : integer; | |
| temp : integer; | |
| scanright, scanleft : integer; | |
| begin | |
| if (first < last) then | |
| begin | |
| pivot := letters[first]; | |
| scanright := first; | |
| scanleft := last; | |
| while scanright < scanleft do | |
| begin | |
| if Counts[letters[scanright+1]] < Counts[pivot] then | |
| begin | |
| if Counts[letters[scanleft]] >= Counts[pivot] then | |
| begin | |
| temp := letters[scanleft]; | |
| inc(scanright); | |
| letters[scanleft] := letters[scanright]; | |
| letters[scanright] := temp; | |
| dec(scanleft); | |
| end | |
| else | |
| dec(scanleft); | |
| end | |
| else | |
| inc(scanright); | |
| end; | |
| temp := letters[scanright]; | |
| letters[scanright] := letters[first]; | |
| letters[first] := temp; | |
| quick(first, scanright-1); | |
| quick(scanright+1, last); | |
| end; | |
| end; | |
| begin {quicksort} | |
| quick(0, 255); | |
| end; | |
| var | |
| i,LastEntry,LastLetter,PrevLetter,InsertAt : integer; | |
| begin { BuildCode } | |
| for i:=0 to 255 do | |
| letters[i] := i; { Initialize to match counts } | |
| RevSort; { Sort into decreasing frequency } | |
| for i :=256 to MaxNode do | |
| begin | |
| { Create node by combining last two entries } | |
| LastEntry := 511-i; | |
| LastLetter := Letters[LastEntry]; | |
| PrevLetter := Letters[LastEntry-1]; | |
| Decoder[i,0] := PrevLetter; | |
| Decoder[i,1] := LastLetter; | |
| Counts[i] := Counts[PrevLetter] + Counts[LastLetter]; | |
| { Find where to insert it } | |
| InsertAt := LastEntry-1; | |
| While (InsertAt > 0) and (Counts[Letters[InsertAt-1]] <= Counts[i]) do | |
| dec(InsertAt); | |
| { Insert the node } | |
| Move(Letters[InsertAt],Letters[InsertAt+1], | |
| (LastEntry-1-InsertAt)*sizeof(Integer)); | |
| Letters[InsertAt] := i; | |
| end; | |
| BuildEncoder(false); | |
| end; | |
| procedure THuffmanFilter.BuildEncoder(verify:boolean); | |
| var | |
| i,code : integer; | |
| j : TBit; | |
| begin | |
| fillchar(EncodeBits,sizeof(EncodeBits),0); | |
| if verify then | |
| begin | |
| { First, confirm that all the Decoder values are in range } | |
| for i:=256 to MaxNode do | |
| for j:=0 to 1 do | |
| if (Decoder[i,j] < 0) or (Decoder[i,j] > MaxNode) then | |
| begin | |
| Error(stIntegrity,i); | |
| exit; | |
| end; | |
| { Initialize the EncodeStates to illegal values to detect missing | |
| codes } | |
| fillchar(EncodeStates,sizeof(EncodeStates),0); | |
| end; | |
| for i:=256 to MaxNode do | |
| begin | |
| EncodeStates[Decoder[i,0]] := i; | |
| code := Decoder[i,1]; | |
| EncodeStates := i; | |
| EncodeBits := 1; | |
| end; | |
| if verify then | |
| for i:=0 to pred(MaxNode) do | |
| if EncodeStates[i] = 0 then | |
| begin | |
| Error(stIntegrity,i); | |
| exit; | |
| end; | |
| end; | |
| function THuffmanFilter.CodeBits(b:byte):word; | |
| var | |
| state : 0..MaxNode; | |
| result : word; | |
| begin | |
| result := 0; | |
| state := b; | |
| while state < MaxNode do | |
| begin | |
| inc(result); | |
| state := EncodeStates[state]; | |
| end; | |
| CodeBits := result; | |
| end; | |
| function THuffmanFilter.PredictedSize:longint; | |
| var | |
| bitcount : longint; | |
| b : byte; | |
| begin | |
| bitcount := 0; | |
| for b:=0 to 255 do | |
| inc(bitcount,Counts[b]*CodeBits(b)); | |
| PredictedSize := (bitcount+7) div 8; | |
| end; | |
| procedure THuffmanFilter.Read(var buf;Count:word); | |
| var | |
| i : word; | |
| bbuf : TByteArray absolute buf; | |
| State : 0..MaxNode; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if learning then | |
| TBitFilter.Read(buf,Count) | |
| else | |
| for i:=0 to Count-1 do | |
| begin | |
| State := MaxNode; | |
| repeat | |
| State := Decoder[State,GetBit]; | |
| until State < 256; | |
| bbuf[i] := State; | |
| end; | |
| for i:=0 to Count-1 do | |
| inc(Counts[bbuf[i]]); | |
| inc(position,Count); | |
| if Position>Size then | |
| Size := Position; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure THuffmanFilter.Write(var buf;Count:word); | |
| var | |
| bbuf : TByteArray absolute buf; | |
| i : word; | |
| bitstack : word; | |
| bitcount : word; | |
| words : word; | |
| state : 0..MaxNode; | |
| begin | |
| if CheckStatus then | |
| begin | |
| for i:=0 to Count-1 do | |
| inc(Counts[bbuf[i]]); | |
| if not learning then | |
| begin | |
| for i:=0 to Count-1 do | |
| begin | |
| bitstack := 0; | |
| bitcount := 0; | |
| words := 0; | |
| state := bbuf[i]; | |
| { Push all the bits onto the stack } | |
| while state < MaxNode do | |
| begin | |
| bitstack := 2*bitstack + EncodeBits[state]; | |
| inc(bitcount); | |
| if bitcount = 16 then | |
| begin | |
| asm | |
| push bitstack | |
| end; | |
| bitstack := 0; | |
| bitcount := 0; | |
| inc(words); | |
| end; | |
| state := EncodeStates[state]; | |
| end; | |
| { Now write out all the bits } | |
| WriteBits(bitstack,bitcount); | |
| while words > 0 do | |
| begin | |
| asm | |
| pop bitstack | |
| end; | |
| WriteBits(BitStack,16); | |
| dec(words); | |
| end; | |
| end; | |
| inc(position,count); | |
| if position>size then | |
| size := position; | |
| CheckBase; | |
| end; | |
| end; | |
| end; | |
| function THuffmanFilter.GetPos:longint; | |
| begin | |
| GetPos := Position; | |
| end; | |
| function THuffmanFilter.GetSize:longint; | |
| begin | |
| GetSize := Size; | |
| end; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| program logdemo; | |
| { Demonstrates use of TLogFilter } | |
| {$i stdefine.inc} | |
| uses | |
| {$ifdef windows} wincrt, {$endif} | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| streams; | |
| var | |
| i : integer; | |
| inlog,log : PLogFilter; | |
| begin | |
| { Log both input and output to Logdemo.out } | |
| new(log, init( new(PDOSStream, init('Logdemo.out',stCreate)))); | |
| log^.log(input); | |
| log^.log(output); | |
| writeln('This is the Logdemo program, which logs input and output'); | |
| writeln('to LOGDEMO.OUT'); | |
| write('Enter an integer:'); | |
| readln(i); | |
| writeln('Logging will now be turned off.'); | |
| if not log^.unlog(input) then; { This is one way to stop logging. } | |
| close(output); { This is another way. } | |
| { Re-open output; input was never closed. } | |
| rewrite(output); | |
| writeln('This line will not be logged.'); | |
| write('Enter another integer:'); | |
| readln(i); | |
| writeln('Logging will be turned back on now.'); | |
| log^.log(input); | |
| log^.log(output); | |
| writeln('This line will be logged to the file.'); | |
| writeln('All done now; close the log.'); | |
| dispose(log,done); | |
| writeln('The log has been closed, so this line won''t be logged.'); | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| PAGE 60, 132 | |
| TITLE 12 bit LZW Compression Scheme | |
| LOCALS @@ | |
| Comment * | |
| This unit is a modified version of Wilbert van Leijen's LZW unit, | |
| to implement a stream type that automatically compresses data. | |
| The original documentation: | |
| This modules implements a 12-bit Lempel-Zev-Welch "crunch" (compress | |
| data) and "uncrunch" (restore data in its original form) routines. | |
| InBuffer and OutBuffer are untyped; you must pass pointers to arrays | |
| (0..MaxRange) of Char or Byte to it, whereby MaxRange is limited to | |
| 2^16-16+1 = 65521 bytes. | |
| You must also supply the size of the input buffer. | |
| The LZW technique is well explained in the following reference: | |
| Terry A. Welch, "A Technique for High Performance Data Compression" | |
| IEEE Computer | |
| Vol. 17, no. 6 (June 1984), pp. 8-19 | |
| Incorporate these routines as follows in a TP unit: | |
| [ deleted ] | |
| (C) Copyright Wilbert van Leijen, Amsterdam 1990. | |
| Released to the Public Domain under the condition that this program | |
| will not be sold for a profit except with written permission from | |
| the author. | |
| Stream additions (c) copyright D.J. Murdoch, 1991. | |
| * | |
| MaxStack = 4096; ; Decompression stack size | |
| TableSize = MaxStack-1; ; Upper bound of 12 bit tables | |
| HalfFull = MaxStack / 2 | |
| ProbeValue = 131; ; Preferably a prime number | |
| True = 1 | |
| False = 0 | |
| EndList = -1; ; Marks end of a list | |
| NoPrev = -2; ; Code for no previous character | |
| Empty = -3; ; Indicates empty | |
| DataSize = [BP+18] ; Number of bytes input | |
| OutputSize = word ptr [BP+16] ; Max number to output | |
| InputBuf = [BP+12] ; Input data buffer | |
| OutputBuf = [BP+8] ; Output data buffer | |
| Tables = [BP+4] ; Where the tables are | |
| Table STRUC | |
| Collision DB MaxStack DUP(?) ; Hash table entries | |
| PrefixTable DW MaxStack DUP(?) ; Code for preceding stringf | |
| SuffixTable DB MaxStack DUP(?) ; Code for current character | |
| ChildTable DW MaxStack DUP(?) ; Next duplicate in collision list | |
| CharStack DB MaxStack DUP(?) ; Decompression stack | |
| StackPtr DW ? ; Decompression stack depth | |
| Prefix DW ? ; Previous code string | |
| TableUsed DW ? ; # string table entries used | |
| InputPos DW ? ; Index in input buffer | |
| OutputPos DW ? ; Index in output buffer | |
| LastHit DW ? ; Last empty slot in collision table | |
| CodeBuf DW ? ; Temporary code buffer | |
| SaveIP DW ? ; Saved registers between calls | |
| SaveAX DW ? | |
| SaveCX DW ? | |
| SaveDX DW ? | |
| NotFound DB ? ; Character combination found flag | |
| Table ENDS | |
| ; CH register is set aside for final output character (= Suffix) | |
| Code SEGMENT Word Public | |
| ASSUME CS:Code | |
| Public Initialise | |
| Public PutSignature | |
| Public Crunch | |
| Public FlushLZW | |
| Public GetSignature | |
| Public Uncrunch | |
| ; Initialise variables, fill tables | |
| Initialise PROC near | |
| PUSH BP | |
| MOV BP,SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| XOR AX, AX | |
| MOV [BX].InputPos, AX | |
| MOV [BX].OutputPos, AX | |
| MOV [BX].TableUsed, AX | |
| MOV [BX].StackPtr, AX | |
| MOV [BX].CodeBuf, Empty | |
| ; Loop: | |
| ; Clear collision table | |
| ; Set prefix to 'no previous character' code | |
| ; Set child nodes to 'end of list' code | |
| MOV DX, Tablesize | |
| @@1: MOV DI, DX | |
| MOV [BX+DI+Collision], 0 | |
| SHL DI, 1 | |
| MOV [BX+DI+PrefixTable], NoPrev | |
| MOV [BX+DI+ChildTable], EndList | |
| DEC DX | |
| JNS @@1 | |
| ; Loop: | |
| ; Enter all single characters into the hash table | |
| MOV DX, 255 | |
| MOV [BX].Prefix, NoPrev | |
| @@2: MOV CH, DL | |
| CALL MakeEntry | |
| DEC DX | |
| JNS @@2 | |
| MOV [BX].SaveCX,CX | |
| POP DS | |
| POP BP | |
| RETN 4 | |
| Initialise ENDP | |
| ; Hash function: 'fold' number | |
| ; AX := (Prefix shl 5 xor Suffix) and TableSize | |
| ; uses CL,DX | |
| HashNumber MACRO | |
| XOR DX, DX | |
| MOV DL, CH | |
| MOV AX, [BX].Prefix | |
| MOV CL, 5 | |
| SHL AX, CL | |
| XOR AX, DX | |
| AND AX, TableSize | |
| ENDM | |
| ; Store a character combination in the hash table | |
| MakeEntry PROC Near | |
| PUSH DX | |
| HashNumber | |
| ; Rehash is necessary if entry in the hash table is occupied | |
| MOV DI, AX | |
| CMP [BX+DI+Collision], False | |
| JE @@6 | |
| ; Loop: | |
| ; Advance index of ChildTable to last empty list | |
| @@1: MOV DI, AX | |
| SHL DI, 1 | |
| CMP [BX+DI+ChildTable], EndList | |
| JE @@2 | |
| MOV AX, [BX+DI+ChildTable] | |
| JMP Short @@1 | |
| ; If the hash table is less than 50% loaded | |
| ; Increase index with the probing value | |
| @@2: CMP [BX].TableUsed, HalfFull | |
| JAE @@3 | |
| MOV SI, AX | |
| ADD SI, ProbeValue | |
| AND SI, TableSize | |
| JMP Short @@4 | |
| ; Else deal with the clustering problem | |
| ; A simple yet effective solution is to start probing at the | |
| ; empty slot of the hash table found during the previous run | |
| @@3: MOV SI, [BX].LastHit | |
| ; Loop: | |
| ; Probe hash table until an empty slot is found | |
| @@4: CMP [BX+SI+Collision], False | |
| JE @@5 | |
| INC SI | |
| AND SI, TableSize | |
| JMP Short @@4 | |
| ; Found an empty slot, save index in ChildTable | |
| ; Store index in LastHit | |
| @@5: MOV DI, AX | |
| SHL DI, 1 | |
| MOV [BX+DI+ChildTable], SI | |
| MOV DI, SI | |
| MOV [BX].LastHit, SI | |
| ; Return: | |
| ; Indicate hash table slot's been occupied | |
| ; Store character combination in PrefixTable, SuffixTable | |
| ; Indicate 'end of list' in ChildTable | |
| ; Bump table load counter | |
| @@6: MOV [BX+DI+Collision], True | |
| MOV [BX+DI+SuffixTable], CH | |
| SHL DI, 1 | |
| MOV [BX+DI+ChildTable], EndList | |
| MOV AX, [BX].Prefix | |
| MOV [BX+DI+PrefixTable], AX | |
| INC [BX].TableUsed | |
| POP DX | |
| RETN | |
| MakeEntry ENDP | |
| ; Lookup a character combination in the hash table | |
| LookupStr PROC Near | |
| HashNumber | |
| XCHG SI, AX | |
| ; Search through list of hash collision entries for one that match | |
| ; Loop | |
| ; Entry is found if prefix and suffix match | |
| ; If no match, advance index to entry in ChildTable | |
| ; Until entry is found or no such list exists in ChildTable | |
| @@1: MOV DI, SI | |
| MOV AL, [BX+DI+SuffixTable] | |
| CMP AL, CH | |
| JNE @@2 | |
| SHL DI, 1 | |
| MOV AX, [BX+DI+PrefixTable] | |
| CMP AX, [BX].Prefix | |
| JE @@3 | |
| @@2: MOV DI, SI | |
| SHL DI, 1 | |
| MOV SI, [BX+DI+ChildTable] | |
| CMP SI, EndList | |
| JNE @@1 | |
| ; Return index from ChildTable in AX | |
| @@3: XCHG AX, SI | |
| RETN | |
| LookupStr ENDP | |
| ; Retrieve next character from the input buffer | |
| GetChar MACRO | |
| LES DI, InputBuf | |
| ADD DI, [BX].InputPos | |
| MOV AL, ES:[DI] | |
| INC [BX].InputPos | |
| ENDM | |
| ; Store next character in AL into the output buffer | |
| PutChar MACRO | |
| LES DI, OutputBuf | |
| ADD DI, [BX].OutputPos | |
| STOSB | |
| INC [BX].OutputPos | |
| ENDM | |
| ; Retrieve compressed code from input buffer | |
| GetCode PROC Near | |
| MOV SI, [BX].CodeBuf | |
| ; Get first character and store it in a temporary buffer | |
| GetChar | |
| XOR AH, AH | |
| MOV DX, AX | |
| ; If input code is empty | |
| CMP SI, Empty | |
| JNE @@1 | |
| ; Get next character | |
| ; Return 8 bits from first character + 4 bits from second character | |
| ; Save the remaining 4 bits of the second character for next time | |
| GetChar | |
| MOV SI, AX | |
| MOV CL, 4 | |
| SHR AX, CL | |
| MOV DI, AX | |
| MOV AX, DX | |
| SHL AX, CL | |
| ADD AX, DI | |
| JMP @@2 | |
| ; Else | |
| ; Get the last 4 bits from the input code + 8 bits from the second char | |
| @@1: MOV AX, SI | |
| XCHG AH, AL | |
| AND AH, 0Fh | |
| ADD AX, DX | |
| MOV SI, Empty | |
| @@2: | |
| MOV [BX].CodeBuf,SI | |
| RETN | |
| GetCode ENDP | |
| ; Store compressed code in the output buffer | |
| PutCode PROC Near | |
| MOV DI, [BX].CodeBuf | |
| MOV DX, [BX].Prefix | |
| ; If output code is empty | |
| CMP DI, Empty | |
| JNE @@1 | |
| ; Store first 8 bits in the output buffer | |
| ; Save last 4 bits for the next time through | |
| MOV AX, DX | |
| MOV CL, 4 | |
| SHR AX, CL | |
| PutChar | |
| MOV DI, DX | |
| JMP @@2 | |
| ; Else | |
| ; Put out last 4 bits of previous code + first 4 bits of this code | |
| ; Next, put out last 8 bits of this code | |
| ; Indicate output code as empty | |
| @@1: MOV AX, DI | |
| MOV CL, 4 | |
| SHL AX, CL | |
| ADD AL, DH | |
| PutChar | |
| MOV AL, DL | |
| PutChar | |
| MOV DI, Empty | |
| @@2: MOV [BX].CodeBuf,DI | |
| RETN | |
| PutCode ENDP | |
| ; Start the compressor by putting 'LZ' | |
| PutSignature PROC Near | |
| PUSH BP | |
| MOV BP,SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| MOV [BX].OutputPos, 0 | |
| ; Get first character and store it in Suffix | |
| ; There are no character combinations yet | |
| MOV AL, 'L' | |
| MOV CH, AL | |
| MOV [BX].Prefix, NoPrev | |
| CALL LookupStr | |
| MOV [BX].Prefix, AX | |
| ; Get next character | |
| MOV AL, 'Z' | |
| MOV CH, AL | |
| MOV [BX].SaveCX,CX | |
| MOV AL,True ; Return success | |
| POP DS | |
| POP BP | |
| RET 4 | |
| PutSignature ENDP | |
| Crunch PROC Near | |
| PUSH BP | |
| MOV BP,SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| MOV CX,[BX].SaveCX ; get saved registers | |
| MOV [BX].InputPos, 0 | |
| MOV [BX].OutputPos, 0 | |
| DEC OutputSize ; sometimes we write 2 bytes per loop, | |
| ; so reduce this for safety | |
| ; Loop: | |
| ; Process all characters from input buffer | |
| @@1: MOV DX, [BX].InputPos | |
| MOV AX, [BX].OutputPos | |
| CMP DX, DataSize | |
| JAE @@5 | |
| CMP AX, OutputSize | |
| JAE @@5 | |
| ; Lookup the character combination | |
| ; Store if not found and if empty slots are available in the hash table | |
| CALL LookupStr | |
| MOV DI, AX | |
| CMP DI, EndList | |
| JNE @@3 | |
| PUSH DI | |
| CMP [BX].TableUsed, TableSize | |
| JA @@2 | |
| CALL MakeEntry | |
| ; Store code in the output buffer | |
| ; If string is in table, keep looking for longer strings | |
| @@2: CALL PutCode | |
| POP DI | |
| MOV [BX].Prefix, NoPrev | |
| XCHG DX, DI | |
| CALL LookupStr | |
| XCHG DI, DX | |
| MOV [BX].Prefix, AX | |
| JMP Short @@4 | |
| ; Get next character | |
| @@3: MOV [BX].Prefix, DI | |
| @@4: GetChar | |
| MOV CH, AL | |
| JMP Short @@1 | |
| @@5: MOV [BX].SaveCX,CX | |
| ; return number written in AX | |
| ; and number used in DX | |
| POP DS | |
| POP BP | |
| RET 14 | |
| Crunch ENDP | |
| ; Make sure the last code will be written out | |
| ; If the output code <> Empty, store the last 4 pending bits | |
| FlushLZW Proc Near | |
| PUSH BP | |
| MOV BP,SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| MOV CX,[BX].SaveCX ; get saved registers | |
| MOV [BX].InputPos, 0 | |
| MOV [BX].OutputPos, 0 | |
| @@5: CALL PutCode | |
| MOV SI,[BX].CodeBuf | |
| CMP SI, Empty | |
| JE @@7 | |
| MOV AX, SI | |
| MOV CL, 4 | |
| SHL AX, CL | |
| PutChar | |
| ; Return the number of bytes written to the output buffer | |
| @@7: MOV AX, [BX].OutputPos | |
| MOV [BX].SaveCX,CX | |
| POP DS | |
| POP BP | |
| RET 8 | |
| FlushLZW ENDP | |
| ; Run through code extracting single characters from code string until | |
| ; no more characters can be removed. Push these onto the stack. | |
| ; They will be entered in reverse order, and will come out in forwards order | |
| ; when popped off | |
| PushChar PROC Near | |
| @@1: MOV DI, SI | |
| SHL DI, 1 | |
| CMP [BX+DI+PrefixTable], NoPrev | |
| JNE @@2 | |
| RETN | |
| @@2: MOV AL, [BX+SI+SuffixTable] | |
| INC [BX].StackPtr | |
| MOV DI, [BX].StackPtr | |
| MOV [BX+DI+CharStack], AL | |
| SHL SI, 1 | |
| MOV SI, [BX+SI+PrefixTable] | |
| JMP Short @@1 | |
| PushChar ENDP | |
| ; While StackPtr > 0 | |
| ; Pop a character from the stack | |
| PopChar PROC Near | |
| PutChar | |
| MOV DI, [BX].StackPtr | |
| OR DI, DI | |
| JE @@1 | |
| MOV AL, [BX+DI+CharStack] | |
| DEC [BX].StackPtr | |
| RETN | |
| @@1: MOV AX, Empty | |
| RETN | |
| PopChar ENDP | |
| ; Check for correct start of buffer, and initialize registers | |
| GetSignature PROC Near | |
| PUSH BP | |
| MOV BP, SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| ; Get first string and check the corresponding character | |
| ; Keep a copy of this code | |
| CALL GetCode | |
| MOV [BX].Prefix, AX | |
| MOV SI, AX | |
| MOV CH, [BX+SI+SuffixTable] | |
| CMP CH, 'L' | |
| JNE @@2 | |
| CALL GetCode | |
| MOV [BX].SaveDX, AX | |
| ; Do half a run through the old Uncrunch loop | |
| ; ( skip size check ) | |
| MOV [BX].Notfound, False | |
| MOV SI, AX | |
| ; ( skip collision test, & PushChar call ) | |
| MOV CH, [BX+SI+SuffixTable] | |
| CMP CH, 'Z' | |
| JNE @@2 | |
| ; ( do PopChar's MOV AX, Empty ) | |
| MOV [BX].SaveAX,Empty | |
| MOV AL,True ; Success! We're ready for the loop. | |
| JMP @@1 | |
| @@2: MOV AL,False ; It failed! | |
| @@1: MOV [BX].SaveCX,CX | |
| MOV [BX].SaveIP,OFFSET MainLoop | |
| POP DS | |
| POP BP | |
| RET 14 | |
| GetSignature ENDP | |
| UnCrunch PROC Near | |
| PUSH BP | |
| MOV BP,SP | |
| PUSH DS ; save DS | |
| LDS BX,Tables ; get DS:BX to point to the tables | |
| MOV AX,[BX].SaveAX ; get saved registers | |
| MOV CX,[BX].SaveCX | |
| MOV DX,[BX].SaveDX | |
| MOV [BX].InputPos, 0 ; set up string pointers & sizes | |
| MOV [BX].OutputPos, 0 | |
| DEC Word Ptr DataSize ; sometimes we need to read two | |
| JMP [BX].SaveIP | |
| ; Loop: | |
| ; Process all characters from input buffer | |
| ; While the stack is not empty, remove and output all characters from | |
| ; stack which are rest of characters in the string | |
| @@1: MOV DI,[BX].OutputPos ; Check if there's room for more characters | |
| CMP DI,OutputSize | |
| JB @@3 | |
| CALL ExitLoop ; Exit from Uncrunch | |
| Mainloop: | |
| @@3: CMP AX, Empty | |
| JE @@4 | |
| CALL PopChar | |
| JMP Short @@1 | |
| ; If code isn't known, store the follower character of last | |
| ; character of string | |
| @@4: CMP [BX].NotFound, False | |
| JE @@5 | |
| PUSH AX | |
| MOV AL, CL | |
| MOV CH, AL | |
| PutChar | |
| POP AX | |
| ; Check whether there's enough space for another char | |
| @@8: MOV DI,[BX].OutputPos | |
| CMP DI,OutputSize | |
| JB @@5 | |
| ; No space, so quit | |
| CALL ExitLoop | |
| ; If the hash table is not full | |
| ; Enter code into table | |
| ; Make current code the previous code | |
| ; Get next code | |
| @@5: CMP [BX].TableUsed, TableSize | |
| JA @@6 | |
| CALL MakeEntry | |
| @@6: MOV [BX].Prefix, DX | |
| CALL GetCode | |
| XCHG DX, AX | |
| ; Old top of loop | |
| MOV AX, [BX].InputPos | |
| CMP AX, DataSize | |
| JB @@10 | |
| ; Out of data, so exit | |
| CALL ExitLoop | |
| ; Retrieve character from string | |
| ; Keep a copy of it in CL | |
| @@10: MOV [BX].NotFound, False | |
| MOV SI, DX | |
| CMP [BX+SI+Collision], False | |
| JNE @@2 | |
| MOV AL, CH | |
| MOV CL, AL | |
| MOV SI, [BX].Prefix | |
| MOV [BX].NotFound, True | |
| ; Get first character from string | |
| @@2: CALL PushChar | |
| MOV AL, [BX+SI+SuffixTable] | |
| MOV CH, AL | |
| CALL PopChar | |
| JMP @@1 | |
| UnCrunch ENDP | |
| ExitLoop PROC Near ; allows exit from UnCrunch loop | |
| ; and resumption at several places. | |
| POP [BX].SaveIP ; Get address | |
| MOV [BX].SaveAX,AX ; Save registers | |
| MOV [BX].SaveCX,CX | |
| MOV [BX].SaveDX,DX | |
| ; Return the number of bytes written to the output buffer in AX | |
| ; and the number of bytes used in DX | |
| MOV AX, [BX].OutputPos | |
| MOV DX, [BX].InputPos | |
| POP DS | |
| POP BP | |
| RETN 14 | |
| ExitLoop ENDP | |
| Code ENDS | |
| END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit ovr1; | |
| {$O+,F+} | |
| interface | |
| procedure proc1; | |
| implementation | |
| procedure proc1; | |
| begin | |
| writeln('This line is being printed by proc1 in unit ovr1.'); | |
| end; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit ovr2; | |
| {$O+,F+} | |
| interface | |
| procedure proc2; | |
| implementation | |
| procedure proc2; | |
| begin | |
| writeln('This line is being printed by proc2 in unit ovr2.'); | |
| end; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| program ovrdemo; | |
| { Program to demonstrate use of two overlay files. } | |
| {$i stdefine.inc} | |
| {$ifndef overlays} | |
| This program only works in modes that support overlays. | |
| {$endif} | |
| uses | |
| overlay,objects,streams, | |
| ovr1,ovr2; | |
| {$O ovr1} | |
| {$O ovr2} | |
| type | |
| PMessageStream = ^TMessageStream; | |
| TMessageStream = object(TNamedBufStream) | |
| { This stream prints its name every time anything is read from it. } | |
| procedure read(var buf; size:word); virtual; | |
| end; | |
| procedure TMessageStream.Read; | |
| begin | |
| writeln('Reading from ',filename^); | |
| TNamedBufStream.Read(buf,size); | |
| end; | |
| var | |
| stream1, stream2 : PMessageStream; | |
| begin | |
| ovrinit('ovrdemo.ovr'); | |
| writeln('The overlay streams aren''t being used yet.'); | |
| proc1; | |
| proc2; | |
| writeln('Now loading overlays to the two streams.'); | |
| ovrclearbuf; { Make sure no overlay is loaded. } | |
| new(stream1, init('ovrdemo.1',stCreate,2048)); | |
| ovrinitstream(stream1); | |
| proc1; { This loads proc1 to Stream1, but doesn't trigger a read | |
| yet. } | |
| new(stream2, init('ovrdemo.2',stCreate,2048)); | |
| ovrinitstream(stream2); | |
| proc2; { This loads proc2 to Stream2, but again, no read. } | |
| writeln('Now each unit is on a different stream; let''s call them a few '); | |
| writeln('times.'); | |
| proc1; | |
| proc2; | |
| proc1; | |
| proc2; | |
| writeln('Now the overlay streams will be disposed of.'); | |
| OvrDisposeStreams; | |
| writeln('These calls will use the old overlay mechanism.'); | |
| proc1; | |
| proc2; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { This file sets some conditional compilation defines } | |
| {$define overlays} { Assume we'll want the overlay code, but turn | |
| it off in certain cases } | |
| {$ifdef windows} | |
| {$undef overlays} | |
| {$endif} | |
| {$ifdef dpmi} | |
| {$undef overlays} | |
| {$endif} | |
| { Only TPW uses "WObjects"; BP 7 calls the same unit "Objects" } | |
| {$ifdef windows} | |
| {$ifndef ver70} | |
| {$define wobjects} | |
| {$endif} | |
| {$endif} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| STREAMS - TP/BP/TPW unit to supplement TurboVision/ObjectWindows streams | |
| Version 1.6. Copyright D.J. Murdoch (1992,1994). | |
| DESCRIPTION | |
| "Wierd Stream Tricks" might be a good name for this unit. It | |
| contains a miscellaneous collection of objects and procedures, all | |
| on a theme of adding functionality to the streams in Borland's | |
| Turbo Pascal libraries TurboVision and ObjectWindows. | |
| Most of my testing has been in TP 6 and BP 7 real mode; the code | |
| and demos also appear to work in BP 7 protected mode, and compile | |
| under Windows, but use it with care in those "foreign" | |
| environments. | |
| LICENSE | |
| This unit is *not* public domain code. You may use it for | |
| no charge if credit is granted to the author, but you may not sell | |
| it, nor may you claim copyright on it. You may make copies of this | |
| unit and give them to people, but may not charge more than $5 for | |
| the distribution media. In particular, it may not be distributed | |
| on a CD ROM costing more than $5 without prior written permission | |
| from D.J. Murdoch. The Garbo and Simtel CD ROM collections have | |
| permission to distribute it. | |
| A lot of the code in this unit is code that's been made freely | |
| available by others, some of it under their copyright, other parts | |
| public domain. As far as I know, all code included here may be | |
| used for free, provided you acknowledge the author. See the list of | |
| credits at the end for all the (known) authors. | |
| This is the fourth release of the STREAMS unit. There are probably | |
| still bugs; I would really appreciate reports of any, or other | |
| suggestions for improvement. Please send either one to me at one | |
| of the following addresses: | |
| dmurdoch@mast.queensu.ca (Internet) | |
| 71631,122 (Compuserve) | |
| DJ Murdoch at 1:249/1.5 (Fidonet) | |
| D. J. Murdoch | |
| 337 Willingdon Ave. | |
| Kingston, Ontario, Canada | |
| K7L 4J3 | |
| SUMMARY | |
| Hierarchy | |
| TStream (from Objects) | |
| TFilter Base type for filters | |
| TEncryptFilter Encrypts as it writes; decrypts as it reads | |
| TLZWFilter Compresses as it writes; expands as it reads | |
| TTextFilter Provides text file interface to stream | |
| TLogFilter Provides logging of text file activity | |
| TBitFilter Allows bit-oriented I/O | |
| TDupFilter Duplicates output, checks for matching input | |
| TConcatFilter Concatenates two streams together | |
| TLimitFilter Limits I/O to a certain byte range | |
| TLoopFilter Makes stream look like a tape loop | |
| TReverseFilter Reads and writes a stream in reverse order | |
| TSequential Filter that doesn't allow Seek | |
| TChksumFilter Calculates 8 or 16 bit checksum for reads/writes | |
| TCRC16Filter Calculates XMODEM style 16 bit CRC | |
| TCRCARCFilter Calculates ARC style 16 bit CRC | |
| TCRC32Filter Calculates ZIP & Zmodem style 32 bit CRC | |
| TNulStream Eats writes, returns constant on reads | |
| TRAMStream Stream in memory | |
| TEMSStream2 Workaround stream to avoid Borland bug | |
| TXMSStream Stream in XMS (extended) memory | |
| TDOSStream (from Objects) | |
| TBufStream (from Objects) | |
| TNamedBufStream Buffered file stream that knows its name | |
| TTempBufStream Buffered file stream that erases itself when done | |
| TWorkStream Stream that grows as needed | |
| Procedures & functions: | |
| TempStream allocates a temporary stream | |
| StreamName returns a stream type name (for debugging) | |
| OvrInitStream like OvrInitEMS, but buffers overlays on a stream | |
| May be called several times to buffer different | |
| segments on different streams. | |
| OvrDetachStream detaches stream from overlay system | |
| OvrDisposeStreams detaches all streams from overlay system and disposes of | |
| them | |
| OvrSizeNeeded Calculates the size needed to load the rest of the segments | |
| to a stream | |
| OvrLoadAll immediately copies as many overlay segments to the stream | |
| as will fit | |
| xms_MemAvail returns the number of bytes of XMS memory available | |
| xms_MaxAvail returns size in bytes of the largest block of XMS memory | |
| ems_MemAvail returns the number of bytes of EMS memory available | |
| ems_MaxAvail returns size in bytes of the largest block of EMS memory | |
| disk_MemAvail returns the number of bytes of temp disk space available | |
| disk_MaxAvail returns size in bytes of the largest block of disk space | |
| FastCopy Like TStream.CopyFrom, but uses a larger buffer | |
| Reassuring Note: | |
| One concern I've heard from people about using this unit: it's | |
| big. Won't 110K of source add a lot of code to my program, even if I | |
| only need one little part of it? The simple answer is no. I've | |
| tried very hard to write Streams so that cross-links between code | |
| are minimized. If all you need are the XMS routines, that's all | |
| that TP's "smart linker" will link into your .EXE. Same for most | |
| of the other objects and functions. One exception is the TempStream | |
| function (Chapter 20). Because it makes explicit references to 4 | |
| types of streams, one call to it will necessarily pull in support | |
| code for all 4 types. That's only about 6K, though. StreamName is | |
| much worse: it pulls in code for *every* stream type. It should | |
| only be used for debugging. | |
| CONTENTS | |
| 0. Files in archive | |
| a list of the files that should accompany this one | |
| 1. TFilter | |
| a generic object to act as a filter to a stream | |
| 2. TEncryptFilter | |
| a filter which does simple encryption/decryption | |
| 3. TLZWFilter | |
| a filter which does LZW compression/decompression | |
| 4. TTextFilter | |
| a filter to provide the Read/ReadLn/Write/WriteLn | |
| interface to a stream | |
| 5. TLogFilter | |
| a filter to allow logging of activity on text files | |
| 6. TBitFilter | |
| a filter to allow bit level I/O to a stream | |
| 7. TDupFilter | |
| a filter to duplicate output, check for duplicate input | |
| 8. TConcatFilter | |
| a filter to concatenate two streams | |
| 9. TLimitFilter | |
| a filter to limit I/O to a certain byte range | |
| 10. TLoopFilter | |
| a filter to make a stream look like a tape loop: things wrap | |
| around when they reach the ends | |
| 11. TReverseFilter and ReverseBytes | |
| a filter to reverse the apparent byte order of a stream and | |
| a procedure to reverse the byte order of a buffer | |
| 12. TSequential | |
| a generic filter that can't Seek | |
| 13. Checksum/CRC filters: | |
| a collection of filters which calculate checksums and CRC | |
| values as the data goes by | |
| 14. TNulStream | |
| a stream which counts data written to it, and which | |
| returns a constant if you read | |
| 15. TRAMStream | |
| a stream which resides entirely in RAM | |
| 16. TXMSStream | |
| a stream which keeps its data in XMS memory | |
| 17. xxx_MemAvail and xxx_MaxAvail | |
| procedures to show how much memory is free | |
| 18. TNamedBufStream | |
| a buffered file stream which knows its own name | |
| 19. TTempBufStream | |
| a temporary buffered file stream, which deletes itself when | |
| done | |
| 20. TempStream | |
| a procedure to allocate a temporary stream, in RAM, EMS, or | |
| on disk, according to a specified preference | |
| 21. TWorkStream | |
| a temporary stream, which allocates new blocks of mixed | |
| types as it grows | |
| 22. OvrInitStream and related procedures | |
| procedures to allow overlays to be buffered on any | |
| stream or combination of streams | |
| 23. TEMSStream2 | |
| EMS stream with fix for bug in TEMSStream.done | |
| 24. FastCopy | |
| fast copy from one stream to another | |
| 25. Miscellaneous constants and types | |
| 26. Release history and credits | |
| 0. FILES IN ARCHIVE | |
| Streams.doc - this file | |
| Streams.pas - main source file for unit | |
| Lzwstrea.asm - source for LZW compression | |
| StDefine.inc - a few conditional compiler defines | |
| Xmsstrm.inc - source for XMS stream | |
| Crc16.asm - source for 16 bit CRC | |
| Crc32.asm - source for 32 bit CRC | |
| Crcarc.asm - source for ARC-style 16 bit CRC | |
| Lzwstrea.obj - assembled code for external linking | |
| Crc16.obj - " " " " " | |
| Crcarc.obj - " " " " " | |
| Crc32.obj - " " " " " | |
| Demo programs: | |
| Encrypt.pas - encryption program, using TEncryptFilter | |
| Compress.pas - file compressor, using TLZWFilter | |
| Huffman.pas - defines a Huffman encoding filter, using TBitFilter | |
| HuffComp.pas - file compressor using THuffmanFilter | |
| Logdemo.pas - simple demo of TLogFilter | |
| Ovrdemo.pas - simple demo of multiple overlay files | |
| ovr1.pas - one overlaid unit | |
| ovr2.pas - a second overlaid unit | |
| Textdemo.pas - simple demo of TTextFilter | |
| 1. TFilter = object(TStream) | |
| "Filters" are programs which take a file as input and produce a new | |
| file as output. TFilter is an adaptation of this idea to streams. | |
| Every TFilter has a base stream, which can be any kind of stream. | |
| Every TFilter is itself a stream, which means you can read and | |
| write to it. When you do, it just relays your request to the base | |
| stream. | |
| One use of this is in the fact that you can make descendants | |
| of TFilter which massage the data on the way to and from the Base. | |
| Just override the Read and Write (and possibly other) methods, and | |
| you can make changes to the data flowing to or from *any* kind of | |
| stream. No need for special coding for an TEMSStream, or for a | |
| TDOSStream, or whatever. Your code can act on any of them. | |
| Examples of things to do are in the Streams unit: encryption | |
| (TEncryptFilter) and LZW compression (TLZWFilter). | |
| The other main use is to add other kinds of functionality to a | |
| stream. You can't use the formatting capabilities of ReadLn and | |
| WriteLn with standard streams, but if you use a TTextFilter (see | |
| below) you can. | |
| FIELDS | |
| Base : PStream; | |
| TFilter.Base holds the pointer to the base stream. | |
| StartOfs : longint; | |
| TFilter.StartOfs holds the offset in the base stream of offset 0 | |
| in the filter. This allows multiple filters to work on the same | |
| stream; one could work at offset 0, another at offset 1000, etc. | |
| Just position the base to the desired starting offset before | |
| initializing the filter. | |
| OwnsBase: Boolean; | |
| If false, then the Base stream is assumed to be owned by some | |
| other part of the program, so that when Done is called, it won't | |
| be disposed of. Normally OwnsBase is true, and it's assumed | |
| that all management of the Base stream will be done by the | |
| TFilter. | |
| METHODS | |
| Constructor Init(ABase: PStream); | |
| TFilter.Init sets Base to point to ABase^, and sets StartOfs to | |
| the current position of the base stream. Sets ByReference to | |
| false. | |
| Destructor Done; virtual; | |
| If Base is not nil, then TFilter.Done calls Flush, then if | |
| OwnsBase is true, disposes of the base stream before calling | |
| TStream.Done for itself. | |
| Function CheckStatus : boolean; virtual; | |
| Returns true if status is stOK. If it is, but the base status is | |
| not stOK, then it assumes that someone has called a Reset for the | |
| filter, so it calls Reset for the base stream. Borland should | |
| have made Reset virtual, and this kludge wouldn't have been | |
| necessary! | |
| Procedure CheckBase; | |
| Checks the base stream for an error. If Base^.status is not | |
| stOK, then it calls Error with Code=stBaseError and | |
| Info=Base^.status. | |
| Function GetPos : longint; virtual; | |
| Function GetSize : longint; virtual; | |
| Procedure Read(var buf; count : word); virtual; | |
| Procedure Seek(pos: longint); virtual; | |
| Procedure Truncate; virtual; | |
| Procedure Write(var buf; count: word); virtual; | |
| Procedure Flush; virtual; | |
| These methods all call the corresponding method in the base | |
| stream. Offsets are translated using StartOfs. Before the call, | |
| CheckStatus is called to propagate any Reset to the base; after | |
| the call, CheckBase is called to propagate any errors to the | |
| filter from the base. | |
| 2. TEncryptFilter = object(TFilter) | |
| This is a filter which does simple encryption/decryption on the | |
| base stream. The encryption method is to XOR each byte with a | |
| Random(256) value; the starting RandSeed is the key for the | |
| encryption. | |
| FIELD | |
| key : longint; | |
| The key is used as a Randseed replacement value. Randseed itself | |
| is left unmodified by Read and Write. | |
| METHODS | |
| Constructor Init(Akey:longint; ABase:PStream); | |
| The value AKey is used as the starting key for byte 0 of the | |
| filter. Anything read from ABase is decrypted using this key; | |
| anything written to it is encrypted. | |
| Procedure Read(var buf; count : word); virtual; | |
| Procedure Seek(pos : longint); virtual; | |
| Procedure Write(var buf; count: word); virtual; | |
| These methods all encrypt/decrypt and update the value of Key to | |
| correspond to the new position in the stream. The encryption | |
| method uses the same algorithm for encryption as for decryption. | |
| TEncryptFilter.Seek is fairly slow, because it updates the Key | |
| once for every byte difference between the current position and | |
| the new position. Moving backwards is slower, because the update | |
| algorithm is written in Pascal, not assembler as is the forward | |
| algorithm. | |
| 3. TLZWFilter = object(TFilter) | |
| This is a filter which does LZW compression as it writes to and | |
| decompression as it reads from the base stream. The LZW code is | |
| an adaptation of Wilbert van Leijen's implementation of the 12 | |
| bit LZW algorithm. It's quite fast, though not as fast as | |
| Wilbert's version; it gets slowed down a lot by having to save | |
| its state after every Read and Write operation. You're best off | |
| writing large chunks to it to take advantage of Wilbert's | |
| excellent code. (Historical note: Trying to rewrite this was | |
| what inspired me to write FULLDB, a program which allows source | |
| level debugging of external .ASM files. -djm) | |
| Each TLZWFilter takes over 28700 bytes of memory, because it | |
| keeps extensive tables to record the current state. | |
| One limitation of the TLZWFilter is that it can only be opened in | |
| read or write mode, and Seek cannot be used in write mode. If | |
| this doesn't suit your application, you might want to flesh out | |
| the demo Huffman encoder (HUFFMAN.PAS), since it does allow | |
| random access. | |
| FIELDS | |
| Mode : word; | |
| One of stOpenRead or stOpenWrite; the mode under which this | |
| filter was opened. | |
| Size : longint; | |
| The size of the expanded stream. This is the size that users of | |
| the filter will see; if compression is working, it will generally | |
| be bigger than Base^.GetSize. | |
| Position : longint; | |
| This is the position in the expanded stream. | |
| Tables : PLZWTables; | |
| This is a pointer to the tables used by the compression engine. | |
| They're automatically allocated on the heap. | |
| METHODS | |
| Constructor Init(ABase:PStream; AMode:TOpenMode); | |
| Allocates Tables from the heap, and opens the compressor in | |
| a mode of either stOpenRead or stOpenWrite. If reading, a | |
| signature and record of the uncompressed filesize is read from | |
| the base to confirm that it is compressed by LZW, and to prime | |
| the Tables. If writing, the signature is written to the stream. | |
| Destructor Done; virtual; | |
| Flushes all data to the stream, and writes the uncompressed | |
| filesize to the head of it before calling TFilter.done. | |
| Procedure Flush; virtual; | |
| Function GetPos: longint; virtual; | |
| Function GetSize:longint; virtual; | |
| Procedure Read(var buf; count:word); virtual; | |
| Procedure Seek(pos:longint); virtual; | |
| Procedure Truncate; virtual; | |
| Procedure Write(var buf; count: word); virtual; | |
| These methods all override the basic filter methods and | |
| compress/decompress the data. They check whether the operation | |
| requested can be performed in the current mode, and call Error | |
| with Code=stBadMode and Info=Mode if the operation is not | |
| supported. | |
| Seek is not supported at all in Write mode. In Read mode, it is | |
| slow for seeking forwards, and very slow for seeking backwards: | |
| it rewinds the file to the start and seeks forward from there by | |
| expanding everything. | |
| Truncate is not supported in either mode, and always causes a | |
| call to Error. | |
| Flush may only be called once; it changes Mode to 0, so any | |
| further operations will fail. | |
| 4. TTextFilter = object(TFilter) | |
| This is a filter which provides a Read/ReadLn/Write/WriteLn | |
| interface to a stream through its Textfile field or through any | |
| other Text variable. Once the filter is initialized, the | |
| Textfile field acts exactly like a text file in the standard I/O | |
| procedures, but reads come from the stream, and writes go to it. | |
| All of the standard stream methods work as well. | |
| You can also use the AssignStream method to redirect I/O for | |
| another Text variable (e.g. System.Input or System.Output) | |
| through the filter. | |
| Warning: The mechanism for reporting errors on the stream uses | |
| the standard Turbo run-time I/O errors. I've tried to map the | |
| stream errors onto corresponding file errors, but sometimes the | |
| error message may be a little misleading. | |
| FIELD | |
| Textfile : Text; | |
| This is the dummy text file used by default in | |
| Read/ReadLn/Write/WriteLn. | |
| TextPtr : ^Text; | |
| This points to the currently assigned text file; typically that | |
| will be TextFile, but if you use the AssignStream method, it may | |
| be different. | |
| METHODS | |
| Constructor Init(ABase:PStream; AName:String); | |
| Initializes the filter with base ABase. The name AName is stored | |
| as the name in the Textfile variable. | |
| Destructor Done; virtual; | |
| Closes the TextFile variable to flush any remaining data to the | |
| stream, and then disposes of the base stream. | |
| Procedure AssignStream(var NewText:text; AName:string); | |
| Closes the current file attached to the stream, then assigns the | |
| new one. You'll still need to open it for reading or writing, as | |
| with the standard Assign procedure. | |
| Procedure Flush; virtual; | |
| Flushes the TextPtr^ variable to the base stream, then flushes the | |
| base stream. | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| Versions of the standard filter functions. These may be | |
| intermixed with text access to the Text variable. | |
| 5. TLogFilter = object(TFilter) | |
| This filter allows logging of activity on text files to a stream. | |
| Logging is done very transparently. Once the TTextFilter is | |
| initialized, you call the Log method to start logging a | |
| particular text file, and the UnLog method to stop. When logging | |
| is in effect, any data read from or written to the text file is | |
| copied to the stream. | |
| Multiple files may be logged to the same stream. For example, | |
| you can log both Input and Output, and keep a record of an | |
| interactive session on the stream. | |
| It's also possible to log the same file to multiple streams. | |
| Just create the different TLogFilter objects, and call their Log | |
| methods with the same file as an argument. If you then call | |
| Unlog, you must do it in the *reverse* order to the order you | |
| called Log, e.g. | |
| S1^.log(output); | |
| S2^.log(output); | |
| S2^.unlog(output); | |
| S1^.unlog(output); | |
| is the correct order to log and unlog. | |
| One detail of the implementation may cause some trouble. The | |
| data is logged to the stream at the time it is written to disk by | |
| the text file. Since text files are buffered, this may not be | |
| the time at which you write to the text file, and multiple text | |
| files logged to the same stream may not have all data appearing | |
| in the correct order. If this is a problem for you, call the | |
| System unit Flush() procedure to flush output from the text file | |
| to disk. | |
| FIELD | |
| LogList : ^Text; | |
| This is a pointer to the first text file currently being logged. | |
| METHODS | |
| Constructor Init(ABase:PStream); | |
| Initializes the filter with a LogList of nil. | |
| Destructor done; virtual; | |
| Stops logging all text files by calling UnLog for each, and | |
| closes and disposes of the base stream. | |
| Procedure Log(var F:text); | |
| Starts logging the text file F. Continues until the UnLog method | |
| is called or the file is closed or Assign'd. | |
| Function UnLog(var F:text):boolean; | |
| Stops logging the text file F. Returns true if successful, false | |
| if not. Will fail if an Assign has been done to F, or F has been | |
| closed, or F has already been Unlogged, or another stream has | |
| started logging F and hasn't been UnLog'd yet. | |
| 6. TBitFilter = object(TFilter) | |
| This filter allows you to do bit-oriented I/O to a stream, | |
| instead of the usual byte-oriented I/O. A typical use might be | |
| for a compression scheme: if a word only takes the values 0 or | |
| 1, you can use a TBitFilter to be sure that it only takes up one | |
| bit of the output file. | |
| If you use this filter, you can mix standard byte-oriented I/O | |
| (Read, Write) with bit-oriented I/O (GetBit, GetBits, ReadBits, | |
| PutBit, PutBits, WriteBits). There's a substantial performance | |
| penalty however, if the bytes you write to the stream aren't | |
| aligned with the actual bytes in the base stream. | |
| One arbitrary choice was made: the order of bits in a byte. | |
| This object follows the 80x86 convention of least significant | |
| part first, and considers the least significant bit (low bit) to | |
| come before the most significant bit (high bit) within a byte. | |
| FIELDS | |
| BitPos : shortint; | |
| Position of stream relative to base file. Negative values signal | |
| that the buffer is unchanged from the file, positive values signal | |
| that the file needs to be updated. Zero signals an empty buffer. | |
| Mask : byte; | |
| The mask to use to extract the next bit from the buffer. | |
| Buffer : byte; | |
| A buffer to hold 8 bits. | |
| AtEnd : boolean; | |
| This flag is an efficiency hack. A TBitFilter can write a single | |
| bit anywhere in a file; this means that it has to read the | |
| existing byte before writing a new one. If it's doing a lot of | |
| small writes at the end of the file, this can be incredibly slow, | |
| because every one of those reads will fail. The AtEnd flag | |
| signals that the current position of the stream is the end of the | |
| file, and a read isn't necessary. | |
| Normally, AtEnd is maintained internally, and you don't need to | |
| worry about it. However, if you increase the length of the base | |
| stream other than by writes through the TBitFilter, you | |
| *must* set AtEnd to false, so that the bit buffer is properly | |
| initialized. Calling Flush on the TBitFilter is sufficient to do | |
| this, and would probably be a good idea before you fiddled with | |
| the base anyways. | |
| METHODS | |
| Function GetBit : TBit; | |
| Type TBit is a 0 or a 1. This function reads a single bit from | |
| the stream. | |
| Function GetBits(count : byte): longint; | |
| This function reads up to 32 bits, and returns them in the | |
| longint value. The first bit read ends up in the least | |
| significant bit of the result, and bits higher than the count | |
| are zeroed. | |
| Procedure ReadBits(var buf; count : longint); | |
| This procedure reads up to 524288 bits (64K bytes) from the | |
| stream into the buffer. Reads are faster when they start on a | |
| byte boundary. Only the bytes that are affected by the | |
| read will be modified, with the high bits of the last byte zeroed | |
| if necessary. For example, if you read fewer than 9 bits into a | |
| word variable, the high byte of the word will be left untouched. | |
| Procedure PutBit(ABit : TBit); | |
| Write one bit to the stream. | |
| Procedure PutBits(Abits : longint; count : byte); | |
| Writes up to 32 bits to the stream. Since ABits is a value | |
| parameter, you can write the value of an expression directly to | |
| the stream without storing it in an intermediate variable. | |
| Procedure WriteBits(var buf; count : longint); | |
| This procedure writes up to 524288 bits (64K bytes) to the | |
| stream. Writes are *much* faster if the starting bit is byte | |
| aligned. | |
| Procedure SeekBit(bitpos:longint); | |
| Seek to a particular bit in the stream. | |
| Function GetBitPos : longint; | |
| Get the current stream position accurate to the nearest bit. | |
| Procedure CopyBits(var S:TBitFilter; Count:longint); | |
| Like CopyFrom, but for bits: copies Count bits from S. | |
| Procedure ByteAlign; | |
| Seeks forward to the next byte boundary. If the last byte | |
| of the stream is only partially filled, it will be padded with | |
| zero bits. | |
| Procedure Read(var Buf; Count : Word); virtual; | |
| Procedure Write(var Buf; Count : Word); virtual; | |
| Procedure Seek(Pos : LongInt); virtual; | |
| Procedure Flush; virtual; | |
| Implement the standard byte-oriented functions. Note that if the | |
| base stream is not byte-aligned at the start or a read/write, | |
| they will split each byte between two in the output stream. | |
| Procedure PrepareBuffer(ForRead:boolean); | |
| Internal method to prepare the buffer for a read or a write. | |
| 7. TDupFilter = object(TFilter) | |
| A filter which duplicates writes to two base streams, and checks | |
| that data read from the two base streams matches. The match test | |
| is a virtual method, so a descendant could implement a different | |
| test if desired. | |
| FIELDS | |
| Base2 : PStream; | |
| This is a pointer to the second base stream. I/O is done first | |
| to Base^, then to Base2^. | |
| Startofs2 : Longint; | |
| Corresponds to TFilter.StartOfs, but applies to Base2^: gives | |
| the offset corresponding to the filter's offset of 0. | |
| METHODS | |
| constructor Init(ABase, Abase2 : PStream); | |
| Sets the two base streams and the start offset variables. | |
| destructor Done; virtual; | |
| Flushes the filter, then disposes of both bases if they're not | |
| nil. | |
| function MisMatch(var buf1,buf2; count:word):word; virtual; | |
| Checks for a mismatch between the two buffers. Returns | |
| the byte number of the mismatch (1 based), or 0 if they | |
| test equal. This default method checks for an exact match. | |
| procedure Read(var Buf; Count : Word); virtual; | |
| Reads from Base into the buffer, then does a corresponding read | |
| from Base2 into a local buffer calling MisMatch to check for a | |
| mismatch. If one is found, calls the Error method to signal an | |
| stMisMatch error, with the Info word equal to the position of | |
| the mismatch in the current buffer. | |
| Unless a base error occurs, the two bases will be left | |
| synchronized at the position following the data just read. | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| procedure Flush; virtual; | |
| Standard methods applied to both bases. | |
| function CheckStatus : Boolean; virtual; | |
| Checks the status of the filter, and assures that both bases are | |
| okay if status is stOK. | |
| procedure CheckBase2; | |
| Like TFilter.CheckBase, but operates on base2 and signals | |
| stBase2Error. | |
| 8. TConcatFilter = object(TFilter) | |
| This is another two-base filter. Unlike TDupFilter, however, | |
| instead of treating the two bases as overlapping, this one | |
| treats Base2 as if it follows directly after the end of Base. | |
| Both reads and writes are directed to the appropriate base | |
| stream. | |
| FIELDS | |
| Base2 : PStream; | |
| This is a pointer to the second base stream, whose contents will | |
| appear to follow those of the Base stream. | |
| Startofs2 : Longint; | |
| Corresponds to TFilter.StartOfs, but applies to Base2^: gives | |
| the offset corresponding to the filter's offset of 0. | |
| Position : Longint; | |
| This is the current position of the TConcatFilter. The | |
| corresponding base stream to this position is kept synchronized | |
| with it. | |
| Base1Size : Longint; | |
| This is the size of the first base stream upon initialization, | |
| and is used in determining when to switch. The first base will | |
| never change in size; all writes to positions bigger than | |
| Base1Size will be directed to the second stream. | |
| METHODS | |
| constructor Init(ABase, Abase2 : PStream); | |
| Sets the two base streams and the fields. The initial position | |
| of the stream is set to the beginning of Base2. | |
| destructor Done; virtual; | |
| Flushes the filter, then disposes of both bases if they're not | |
| nil. | |
| procedure Flush; virtual; | |
| function GetPos:longint; virtual; | |
| function GetSize:longint; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| These methods work directly on Base until its size is reached, | |
| then switch over to Base2. Base will *never* grow from the size | |
| at stream initialization. | |
| function CheckStatus : Boolean; virtual; | |
| Checks the status of the filter, and assures that both bases are | |
| okay if status is stOK. | |
| procedure CheckBase2; | |
| Like TFilter.CheckBase, but operates on base2 and signals | |
| stBase2Error. | |
| 9. TLimitFilter = object(TFilter) | |
| This is a filter that applies upper and lower limits to the | |
| bytes that may be accessed from the base stream. You can use it | |
| in debugging, to detect out of range access; in programs that | |
| allocate workspace, to limit their impact on the system; and | |
| in other situations where you want to undo stream | |
| concatenations. | |
| FIELDS | |
| LoLimit, HiLimit : longint; | |
| These two fields define the limits for accesses. The current | |
| position of the filter will always satisfy | |
| LoLimit <= GetPos <= HiLimit | |
| attempt to move it outside these limits will cause an error. | |
| METHODS | |
| constructor Init(ABase:PStream; ALoLimit,AHiLimit:Longint); | |
| Does the usual TFilter.Init, sets the limits, then does a Seek | |
| to ALoLimit if it is non-zero. | |
| function GetSize:Longint; | |
| Returns the smaller of HiLimit and TFilter.GetSize. | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| These check that the request won't move the current position | |
| outside the limits, and raise an error if so. If not, the | |
| request is passed on to the TFilter method. | |
| 10. TLoopFilter = object(TLimitFilter) | |
| This filter makes a stream look like a tape loop: if you read | |
| or write beyond the end, it loops back to the beginning. It's | |
| descended from a TLimitFilter and uses the LoLimit and the | |
| HiLimit as the ends of the part of the base stream that you can | |
| see. | |
| METHODS | |
| function GetSize:Longint; | |
| Returns the smaller of the length between the limits, or the | |
| length from the lower limit to the end of the base stream. | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| These all act as though on an infinitely repeating stream, by | |
| mapping all requests back into the stream range. | |
| 11. TReverseFilter = object(TFilter) and ReverseBytes | |
| This filter lets you read or write a stream in reverse order: | |
| both Read and Write act on the block of bytes *before* the | |
| current file position. The GetPos and Seek methods also work in | |
| the reversed byte order, i.e. counting bytes from the end of the | |
| stream instead of from the beginning. | |
| FIELD | |
| ReverseBlocks : Boolean; | |
| When you use a TReverseFilter, the question comes up as to | |
| whether you want to read structures in the order in which they | |
| were written, or whether you actually want to reverse the byte | |
| order within each structure. This field is used to determine that. | |
| If ReverseBlocks is true, then the byte order appears completely | |
| reversed. If it's false, then when you do a read of more than 1 | |
| byte, the buffer gets the bytes in their original order. | |
| METHODS | |
| constructor init(ABase:PStream; AReverseBlocks:boolean); | |
| The constructor calls the standard TFilter.Init constructor, | |
| and initializes the AReverseBlocks field. | |
| function GetPos:longint; | |
| procedure Read(var Buf; Count : Word); | |
| procedure Write(var Buf; Count : Word); | |
| procedure Seek(Pos : LongInt); | |
| These methods all act on the stream in the reverse byte order. | |
| The action of Read and Write depends on the ReverseBlocks field | |
| described above. | |
| procedure Truncate; | |
| Truncation is not supported, and a call to this method will | |
| trigger an stUnsupported error. | |
| PROCEDURE | |
| procedure ReverseBytes(var Buf; Count : Word); | |
| This procedure reverses the byte order within the buffer Buf. | |
| It's not a TReverseFilter method, just a standard procedure. | |
| 12. TSequential = object(TFilter) | |
| This is a very simple descendant of TFilter: the only | |
| difference is that it signals stUnsupported if the Seek method | |
| is called. It's used as a base for the filters below, which can | |
| only work sequentially. | |
| METHOD | |
| procedure Seek(pos:longint); virtual; | |
| Signals stUnsupported by calling Error if ever called. | |
| 13. Checksum/CRC filters | |
| These are four filters, TChkSumFilter, TCRC16Filter, | |
| TCRCARCFilter, and TCRC32Filter, which can be used to calculate | |
| checksums and cyclic redundancy checks (CRCs) on the stream of | |
| data passing through the filter in either reads or writes. | |
| All three CRC filters are based on code by a collection of | |
| authors: Stephen Satchell, Chuck Forsberg, Mark G. Mendel, R. | |
| P. Byrne, J. R. Louvau and probably others. Edwin T. Floyd | |
| collected them together and translated them to TASM. The | |
| CRC*.ASM files include more comments about sources and usage. | |
| The basic calculations are also available in the interfaced | |
| procedures UpdateChksum, UpdateCRC16, UpdateCRCARC, UpdateCRC32. | |
| See the source code for the calling details. | |
| TChkSumFilter = object(TSequential) | |
| TChkSumFilter calculates a 16 bit sum of all the bytes read from | |
| or written to the stream. | |
| TCRC16Filter = object(TSequential) | |
| This filter calculates the same 16 bit CRC as used in XModem and | |
| its descendants. | |
| TCRCARCFilter = object(TSequential) | |
| This filter calculates the 16 bit CRC used by ARC. | |
| TCRC32Filter = object(TSequential) | |
| This filter calculates the 32 bit CRC used by PKZIP and ZModem. | |
| FIELDS | |
| TChkSumFilter.Chksum : word; | |
| The 16 bit sum of all bytes passing through the filter. If an 8 | |
| bit checksum is required, get the low byte of Chksum by using | |
| Byte(Chksum). | |
| TCRC16Filter.CRC16 : word; | |
| The XModem-style 16 bit CRC of all bytes passing through the | |
| filter. | |
| TCRCARCFilter.CRCARC : word; | |
| The ARC-style 16 bit CRC of all bytes passing through the | |
| filter. | |
| TCRC32Filter.CRC32 : longint; | |
| The PKZIP and ZModem-style 32 bit CRC of all bytes passing | |
| through the filter. Note that the value should be inverted | |
| before use (i.e. use "not CRC32" rather than "CRC32") for | |
| compatibility with those programs. | |
| METHODS | |
| constructor TChkSumFilter.Init(ABase : PStream;AChksum:word); | |
| constructor TCRC16Filter.Init(ABase : PStream;ACRC16:word); | |
| constructor TCRCARCFilter.Init(ABase : PStream;ACRCARC:word); | |
| constructor TCRC32Filter.Init(ABase : PStream;ACRC32:word); | |
| These constructors all initialize the filter, and set the | |
| sum or CRC to the given value. Typically the first three would | |
| start with a value of 0; PKZIP and ZModem start the 32 bit CRC | |
| with a value of $FFFFFFFF. | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| All four filters override just these two methods. Both update | |
| the sum or CRC if the Read/Write is successful. | |
| 14. TNulStream = object(TStream) | |
| A stream which eats your writes, and returns a constant if you | |
| read from it. (Suggested by Stefan Boether.) | |
| I can see two uses for this stream. Stefan suggested using it to | |
| count bytes: if you're not sure how much space something will | |
| take when written out, write it to a TNulStream, and then read | |
| the size to find out. I use it differently in the TempStream | |
| code: there, it's used to initialize another stream to a fixed | |
| value. I just copy as many bytes as I need from a TNulStream. | |
| FIELDS | |
| position : longint; | |
| The current position of the stream. This is increased by both | |
| reads and writes. It's also used as the current size of the | |
| stream. | |
| value : byte; | |
| The value which will be used to fill any read request. | |
| METHODS | |
| Constructor init(Avalue : byte); | |
| Initialize the stream with value=Avalue. | |
| Destructor done; virtual; | |
| Dispose of the stream. | |
| Function getpos : longint; virtual; | |
| Function getsize : longint; virtual; | |
| Procedure read(var buf; count : word); virtual; | |
| Procedure seek(pos: longint); virtual; | |
| Procedure write(var buf; count: word); virtual; | |
| Implement the basic stream functions. | |
| 15. TRAMStream = object(TStream) | |
| A stream which resides entirely in RAM, either maintaining its | |
| own buffer, or using a predefined buffer. The maximum length | |
| is 65520 bytes. | |
| FIELDS | |
| cp : word; | |
| The current pointer for the stream. | |
| size : word; | |
| The current size of the stream. | |
| alloc : word; | |
| The size of the allocated block of memory. | |
| buffer : Pbyte_array; | |
| A pointer to the block of memory holding the stream data. | |
| OwnMem : Boolean; | |
| Indicates whether the stream "owns" the buffer, and should dispose | |
| of it in the Done destructor. | |
| METHODS | |
| Constructor init(ASize : word); | |
| Attempt to initialize the stream to a block size of Asize; | |
| initial stream size and position are 0. | |
| Constructor UseBuf(Abuffer : pointer; Asize : word); | |
| Set up the stream using the given buffer; OwnMem is set to false, | |
| so that the memory will not be disposed when done. Initial | |
| position is 0 and size is Asize. | |
| Destructor done; virtual; | |
| Dispose of the stream, and if OwnMem is true, the data buffer. | |
| Function getpos : longint; virtual; | |
| Function getsize : longint; virtual; | |
| Procedure read(var buf; count : word); virtual; | |
| Procedure seek(pos: longint); virtual; | |
| Procedure truncate; virtual; | |
| Procedure write(var buf; count: word); virtual; | |
| Implement the basic stream functions. | |
| 16. TXMSStream = object(TStream) | |
| A stream which keeps its data in XMS (extended) memory. | |
| FIELDS | |
| Handle : word; | |
| The handle used by the XMS memory manager for the stream's block | |
| of data. | |
| MaxBlocks : word; | |
| The maximum number of 1K blocks of memory to allocate. | |
| BlocksUsed : word; | |
| The number of 1K blocks of memory currently allocated. Always | |
| allocates at least 1 byte more than Size. | |
| Size : longint; | |
| The current size of the stream. | |
| Position : longint; | |
| The current position of the stream. | |
| METHODS | |
| Constructor init(MinSize,MaxSize : longint); | |
| Attempts to allocate a block of XMS memory of at least MinSize | |
| bytes. MaxSize is treated like the MaxSize parameter of | |
| TEMSStream.Init: the Init will try to allocate a block that can | |
| grow to MaxSize, but won't signal an error if it gets a smaller | |
| one. If you definitely need a certain size, specify it as | |
| MinSize. | |
| Destructor done; virtual; | |
| Dispose of the stream, and release the XMS memory. | |
| Function getpos : longint; virtual; | |
| Function getsize : longint; virtual; | |
| Procedure read(var buf; count : word); virtual; | |
| Procedure seek(pos: longint); virtual; | |
| Procedure truncate; virtual; | |
| Procedure write(var buf; count: word); virtual; | |
| Implement the basic stream functions. | |
| Procedure NewBlock; | |
| Internal method to increase the stream's allocation by one block. | |
| Procedure FreeBlock; | |
| Internal method to decrease the stream's allocation by one block. | |
| Doesn't check if the allocated size falls below Size. | |
| 17. xms_MemAvail, xms_MaxAvail, ems_MemAvail, ems_MaxAvail, | |
| disk_MemAvail, disk_MaxAvail | |
| These procedures are analogous to the MemAvail and MaxAvail | |
| procedures in the System unit. They report on available XMS and | |
| EMS memory, and disk space on the drives mentioned in the | |
| TempEnvVar directories. As of release 1.4, they report in bytes, | |
| just as the standard functions do. | |
| 18. TNamedBufStream = object(TBufStream) | |
| A simple descendant of TBufStream which knows its own name. | |
| FIELD | |
| filename : PString { PChar in TPW }; | |
| The name of the stream. | |
| METHODS | |
| Constructor Init(name:FNameStr;mode:TOpenMode;abufsize:word); | |
| Open the file with the given name, and save the name. | |
| Destructor Done; virtual; | |
| Close the file. | |
| 19. TTempBufStream = object(TNamedBufStream) | |
| A temporary buffered file stream, which deletes itself when done. | |
| It's allocated in one of the directories specified by TempEnvVar. | |
| METHODS | |
| Constructor init(abufsize:word;InitSize,MaxSize : Longint); | |
| Create a temporary file with a unique name, in a directory | |
| pointed to by the environment variable named in TempEnvVar or in | |
| the current directory, and open it in read/write mode. The | |
| constructor will only be successful if the file can be set to | |
| InitSize bytes. The directory chosen will be the first with | |
| at least MaxSize bytes available, or, if none has that much | |
| space, the one with the largest space available. | |
| Destructor done; virtual; | |
| Close and delete the temporary file. | |
| 20. Function TempStream(InitSize,MaxSize : longint; | |
| Preference:TStreamRanking):PStream; | |
| This procedure returns a pointer to a temporary stream from a | |
| choice of 4, specified in the Preference array. The first stream | |
| type listed in the Preference array which can be successfully | |
| created with the given sizes will be returned, or Nil if none can | |
| be made. | |
| ARGUMENTS | |
| Initsize : longint; | |
| The initial size to allocate to the stream. This many nulls will | |
| be written to the stream, and then the position will be set to | |
| byte 0. | |
| MaxSize : longint; | |
| The maximum size to which the stream should be allowed to grow. | |
| The stream will be allocated to a particular type only if there | |
| are at least MaxSize bytes available at the time of the call to | |
| TempStream. However, the space isn't reserved; there's no | |
| guarantee that the space will still be available at the time your | |
| stream grows to that size. | |
| Preference : TStreamRanking; | |
| An array of 4 entries specifying what sort of temporary stream is | |
| desired. Supplied constants include: | |
| ForSpeed = (RAMStream, EMSStream, XMSStream, FileStream); | |
| ForSize = (FileStream,EMSStream, XMSStream, RAMStream); | |
| ForSizeInMem = (EMSStream, XMSStream, RAMStream, NoStream); | |
| ForOverlays = (EMSStream, XMSStream, FileStream, NoStream); | |
| 21. TWorkStream = object(TFilter) | |
| This is a stream that can grow as you write to it. You can | |
| allocate it small at first, and then if it turns out that you | |
| need more space, it'll allocate additional blocks. The nice | |
| feature of this stream as compared to the other kinds of stream | |
| is that when it runs out of one kind of memory (RAM, EMS, XMS, or | |
| disk) it'll move continue in the next. | |
| Though a descendant of TFilter, this type is more of a stream | |
| than a filter, because it manages its own base. It starts out by | |
| allocating one block of the size you specify (see the Init method | |
| for the details). If it has a write error on that block, it | |
| allocates a new one, and uses a TConcatFilter to attach them | |
| together. In effect, this creates a linked list of streams. | |
| FIELDS | |
| Allocate : TAllocator; | |
| This is a procedural parameter pointing to the function which | |
| will be used to allocate the next block. Typically TempStream | |
| would be used for the allocations. | |
| BlockMin, | |
| BlockMax : longint; | |
| Preference : TStreamRanking; | |
| These are the parameters which will be passed to Allocate when a | |
| new block is needed. If you don't use TempStream, your allocator | |
| can interpret them however it likes. | |
| The field Blockmax is also used by the TWorkStream when it does | |
| its writes. It splits up any write that might cross offset | |
| Blockmax in the current block into two writes, one on either | |
| side. This means that any stream like a TRamStream that really | |
| has a known physical size limit will be completely filled before | |
| a write fails and a new block needs to be allocated. | |
| BlockStart: longint; | |
| This is the offset in the stream where the last block (the only | |
| one which can be expanded) starts. | |
| METHODS | |
| constructor Init(Allocator:TAllocator;ABlockmin,ABlockMax:Longint; | |
| APreference : TStreamRanking); | |
| The arguments to Init serve as initial values to the fields with | |
| corresponding names, described above. After those are set, Init | |
| does the first allocation using Allocate, and sets the result as | |
| the base stream. | |
| procedure Write(var Buf; Count:Word); virtual; | |
| This is the only TFilter method which TWorkStream overrides. It | |
| passes most writes through to the base stream, but if an error | |
| occurs, it allocates a new block, concatenates it on to the | |
| existing part of the stream using TConcatFilter, and does the | |
| write there. The only writes which don't go directly to the Base | |
| first are those which would cross the ABlockMax byte of the | |
| current block; they're split up into two parts. | |
| 22. Stream overlay procedures | |
| These procedures allow overlays to be buffered on any stream or | |
| combination of streams. Some overlays can be loaded into EMS, | |
| others kept on disk, and others can be put onto any other | |
| available stream. | |
| PROCEDURES/FUNCTIONS | |
| Procedure OvrInitStream(S:PStream); | |
| Copies overlay segment code to S as new segments are loaded, and | |
| does reloads from there. You may call OvrInitStream multiple | |
| times, and different segments will be buffered on different | |
| streams, depending on the order in which they are loaded by the | |
| overlay loader. | |
| On the first call, an exit handler is installed which will call | |
| OvrDisposeStreams upon program termination. | |
| Procedure OvrDetachStream(BadS:PStream); | |
| Makes sure that the overlay system makes no references to BadS. | |
| Call this before disposing of a stream which has been passed to | |
| OvrInitStream, or you're very likely to crash. | |
| Procedure OvrDisposeStreams; | |
| Detaches and disposes of all streams being used by the overlay | |
| system. | |
| Function OvrSizeNeeded:longint; | |
| Returns the additional size required to load any segments which | |
| still haven't been loaded to a stream. | |
| Function OvrLoadAll:boolean; | |
| Forces all overlay segments to be copied into the stream; if | |
| successful (true) then no more references to the overlay file | |
| will be made. | |
| Warning: This function calls OvrClearBuf, so that any overlay | |
| files which are already in the regular overlay buffer will need | |
| to be reloaded. | |
| 23. TEMSStream2 = object(TEMSStream) | |
| Borland made a small mistake when they wrote TEMSStream. The | |
| done destructor can leave the global variable EMSCurHandle | |
| with the handle of a page that has been freed. If the next | |
| EMS stream happens to get the same handle, TEMSStream won't | |
| bother mapping it into the page frame, because it thinks it's | |
| already there. | |
| TEMSStream2 overrides the Done destructor to fix this problem. | |
| Destructor Done; | |
| Calls TEMSStream.done, then sets EMSCurhandle to $FFFF to signal | |
| that the current page is no longer valid. | |
| 24. procedure FastCopy(var src,dest: TStream; size:longint); | |
| This procedure behaves just like dest.copyfrom(src,size), but | |
| will be quite a bit faster on large copies, because it tries to | |
| use a large buffer on the heap. (Copyfrom uses a 512 byte buffer | |
| on the stack.) If the heap space isn't there, it falls back to | |
| using CopyFrom. | |
| 25. Miscellaneous constants and types | |
| CONSTANTS | |
| stBadMode = 1; | |
| Error signalled when an operation is not permitted in the current | |
| mode. | |
| stStreamFail = 2; | |
| Error signalled when a stream Init failed. | |
| stBaseError = 3; | |
| Error signalled by a TFilter when the base stream has an error; | |
| the base stream's error number is put in the Info field. | |
| stMemError = 4; | |
| Not enough memory for operation. | |
| stSigError = 5; | |
| Problem with LZW file signature. | |
| stUsedAll = 6; | |
| XMS stream has used all of its allowed blocks. | |
| stUnsupported = 7; | |
| Operation unsupported in this stream. TSequential signals this | |
| error if Seek is called. | |
| stBase2Error = 8; | |
| Error in second base in a TDupFilter; Info gets the Base2^.Error | |
| value. | |
| stMisMatch = 9; | |
| The two bases don't match on a read. The Info field is set to | |
| the position of the mismatch in the current read buffer. | |
| BufSize : word = 2048; | |
| Buffer size to use when creating a buffered file stream in | |
| TempStream. | |
| TempEnvVar : String[12] = 'TEMP'; | |
| This is the name of the environment variable to look in for a | |
| list of directories when creating a TTempBufStream, or when | |
| reporting on available space with disk_MaxAvail or disk_MemAvail. | |
| If the environment variable doesn't exist, all the procedures | |
| treat it as though it specifies the current directory. | |
| TYPES | |
| TOpenMode = $3C00..$3DFF; | |
| This is the widest possible range of open modes for a TDOSStream | |
| descendant. Values outside this range can cause very serious bugs in | |
| programs, since the high byte is used as the DOS service number | |
| when the file is opened. | |
| PLZWTables = ^TLZWTables; | |
| TLZWTables = record ... | |
| These tables are used internally to maintain the state of a | |
| TLZWFilter. | |
| PByte_Array = ^TByte_Array; | |
| TByte_Array = array[0..65520] of byte; | |
| An array type used as a buffer in several places. | |
| TStreamType = (NoStream, RAMStream, EMSStream, XMSStream, FileStream); | |
| The types of streams that TempStream can create. | |
| TStreamRanking = array[1..NumTypes] of TStreamType; | |
| An array giving the order from most preferred to least preferred | |
| for a temporary stream. | |
| TAllocator = function (InitSize, MaxSize : LongInt; | |
| Preference : TStreamRanking) : PStream; | |
| A function like TempStream which allocates new streams. One of | |
| these is used by TWorkStream to allocate new blocks. It should | |
| create a stream which is at least InitSize bytes, and likely | |
| capable of growing to MaxSize bytes, if condition don't change. | |
| The Preference array indicates the desired strategy for | |
| allocating. | |
| 26. Release history and credits | |
| 1.0 - First release, missing LZW. Immediately replaced by | |
| 1.1 - First correct release: TFilter, TEncryptFilter, TTextFilter, | |
| TLogFilter, TRAMStream, TNamedBufStream, TTempBufStream, | |
| TempStream, overlay procedures (my ideas), TLZWFilter | |
| (from code by Wilbert van Leijen) | |
| 1.2 - TNulStream, TXMSStream added (from Stefan Boether) | |
| TBitFilter added (suggestion of Rene Seguin) | |
| TFilter.Done calls Flush | |
| TRAMStream.UseBuf and OwnMem added. | |
| 1.3 - TDupFilter, TSequential, TChksumFilter added (my ideas), | |
| TCRC16Filter, TCRCARCFilter, TCRC32Filter and related procedures | |
| added (from code by Edwin T. Floyd, Stephen Satchell, Chuck | |
| Forsberg, Mark G. Mendel, R. P. Byrne, J. R. Louvau and | |
| probably others); TFilter.Flush added; HUFFMAN demo added. | |
| 1.4 - Recoded several of the TRAMStream methods in assembler for | |
| more speed; fixed numerous TTextFilter bugs and added | |
| TTextFilter.AssignStream and TextDemo.pas; fixed | |
| TXMSStream.Seek bug. Changed xms_Memavail and xms_Maxavail | |
| to report in bytes, and added ems_Memavail and ems_Maxavail | |
| (based on code sent to me by Eyal Doron) and disk_Memavail | |
| and disk_Maxavail. Changed TXMSStream.Init to match | |
| TEMSStream.Init. Added TConcatFilter, TLimitFilter, | |
| TLoopFilter, TReverseFilter and TWorkStream. Added OwnsBase | |
| field to TFilter. Did some testing to assure that the unit | |
| works in BP 7 protected mode. Thanks to Max Maschein, Eyal | |
| Doron, and others for bug fix help. | |
| 1.5 - The first public release of the 1.4 enhancements. | |
| 1.6 - Fixed bugs: Getmem returning nil, Reversebytes, termination | |
| of TLZWStream, workaround for Borland's TEMSStream bug, | |
| improved speed of TTextFilter, added FastCopy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| unit Streams; | |
| { Unit to provide enhancements to TV Objects unit streams in the form | |
| of several filters, i.e. stream clients, and other streams. } | |
| {#Z+} { These comments don't need to go into the help file. } | |
| {$B-} { Use fast boolean evaluation. } | |
| { Version 1.2 - Adds TNulStream and TXMSStream, from suggestion and | |
| code by Stefan Boether; TBitFilter, from suggestion | |
| by Rene Seguin; added call to Flush to TFilter.Done; | |
| UseBuf and OwnMem to TRAMStream. | |
| TTextFilter fixed so that mixed access methods work. | |
| 1.3 - Added TDupFilter, TSequential, CRCs and Checksums | |
| 1.4 - Recoded several of the TRAMStream methods in assembler for | |
| more speed; fixed numerous TTextFilter bugs and added | |
| TTextFilter.AssignStream and TextDemo.pas; fixed | |
| TXMSStream.Seek bug. Changed xms_Memavail and xms_Maxavail | |
| to report in bytes, and added ems_Memavail and ems_Maxavail | |
| (based on code sent to me by Eyal Doron) and disk_Memavail | |
| and disk_Maxavail. Changed TXMSStream.Init to match | |
| TEMSStream.Init. Added TConcatFilter, TLimitFilter, | |
| TLoopFilter, TReverseFilter and TWorkStream. Added OwnsBase | |
| field to TFilter. Did some testing to assure that the unit | |
| works in BP 7 protected mode. Thanks to Max Maschein, Eyal | |
| Doron, and others for bug fix help. | |
| 1.5 - The first public release of the 1.4 enhancements. | |
| 1.6 - fixed some bugs when Getmem returns nil | |
| fixed bug in ReverseBytes | |
| fixed bug in termination of TLZWstream | |
| added workaround for TEMSStream bug, FastCopy } | |
| { Load some conditional defines } | |
| {$i STDefine.inc} | |
| {$ifdef overlays} | |
| {$O-} | |
| { Don't overlay this unit; it contains code that needs to participate | |
| in overlay management. } | |
| {$endif} | |
| { Hierarchy: | |
| TStream (from Objects) | |
| TFilter Base type for filters | |
| TEncryptFilter Encrypts as it writes; decrypts as it reads | |
| TLZWFilter Compresses as it writes; expands as it reads | |
| TTextFilter Provides text file interface to stream | |
| TLogFilter Provides logging of text file activity | |
| TBitFilter Allows reads & writes by the bit | |
| TDupFilter Duplicates output, checks for matching input | |
| TConcatFilter Concatenates two streams | |
| TLimitFilter Limits I/O to a specific range | |
| TLoopFilter Joins end of stream to start | |
| TReverseFilter Reads and writes the stream in reverse order | |
| TSequential Filter that doesn't allow Seek | |
| TChksumFilter Calculates 16 bit checksum for reads and writes | |
| TCRC16Filter Calculates XMODEM-style 16 bit CRC | |
| TCRCARCFilter Calculates ARC-style 16 bit CRC | |
| TCRC32Filter Calculates ZIP/ZModem-style 32 bit CRC | |
| TNulStream Eats writes, returns constant on reads | |
| TRAMStream Stream in memory | |
| TEMSStream2 Bug workaround for Borland's TEMSStream | |
| TXMSStream Stream in XMS | |
| TDOSStream (from Objects) | |
| TBufStream (from Objects) | |
| TNamedBufStream Buffered file stream that knows its name | |
| TTempBufStream Buffered file stream that erases itself when done | |
| TWorkStream Stream that grows as needed | |
| Procedures & functions: | |
| TempStream allocates a temporary stream | |
| OvrInitStream like OvrInitEMS, but buffers overlays on a stream | |
| May be called several times to buffer different | |
| segments on different streams. | |
| OvrDetachStream detaches stream from overlay system | |
| OvrDisposeStreams detaches all streams from overlay system and disposes of | |
| them | |
| OvrSizeNeeded Calculates the size needed to load the rest of the segments | |
| to a stream | |
| OvrLoadAll immediately copies as many overlay segments to the stream | |
| as will fit | |
| UpdateChkSum updates a 16 bit checksum value | |
| UpdateCRC16 updates a CRC16 value | |
| UpdateCRCARC updates a CRCARC value | |
| UpdateCRC32 updates a CRC32 value | |
| ReverseBytes reverses the byte order within a buffer | |
| } | |
| {#Z-} | |
| interface | |
| uses | |
| {$ifdef windows} | |
| strings,windos,winprocs, | |
| {$else} | |
| DOS, | |
| {$endif} | |
| {$ifdef overlays} | |
| Overlay, | |
| {$endif} | |
| {$ifdef wobjects} | |
| Wobjects; | |
| {$else} | |
| Objects; | |
| {$endif} | |
| const | |
| stBadMode = 1; { Bad mode for stream - operation not | |
| supported. ErrorInfo = mode. } | |
| stStreamFail = 2; { Stream init failed } | |
| stBaseError = 3; { Error in base stream. ErrorInfo = base error value } | |
| stMemError = 4; { Not enough memory for operation } | |
| stSigError = 5; { Problem with LZ file signature } | |
| stUsedAll = 6; { Used limit of allocation } | |
| stUnsupported = 7; { Operation unsupported in this stream } | |
| stBase2Error = 8; { Error in second base. ErrorInfo = base2 error value } | |
| stMisMatch = 9; { Two bases don't match. ErrorInfo = mismatch position | |
| in current buffer. } | |
| stIntegrity = 10; { Stream has detected an integrity error | |
| in a self check. Info depends on | |
| stream type. } | |
| type | |
| TOpenMode = $3C00..$3DFF; { Allowable DOS stream open modes } | |
| {$ifdef windows} | |
| FNameStr = PChar; { To make streams take names as in the manual. } | |
| {$endif} | |
| PFilter = ^TFilter; | |
| TFilter = | |
| object(TStream) | |
| { Generic object to filter another stream. TFilter just passes everything | |
| through, and mirrors the status of the base stream } | |
| Base : PStream; | |
| { Pointer to the base stream. } | |
| Startofs : LongInt; | |
| { The offset of the start of the filter in the base stream. } | |
| OwnsBase : Boolean; | |
| { Defaults true; if set to false, then #Done# won't dispose of | |
| the base. } | |
| constructor Init(ABase : PStream); | |
| { Initialize the filter with the given base. } | |
| destructor Done; virtual; | |
| { Flush filter, then dispose of base if #OwnsBase#. } | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| procedure Flush; virtual; | |
| function CheckStatus : Boolean; virtual; | |
| { Return true if status is stOK. | |
| If status is stOK, but base is not, then reset the base. This is a poor | |
| substitute for a virtual Reset method. } | |
| procedure CheckBase; | |
| { Check base stream for error, and copy status using own Error method. } | |
| end; | |
| PEncryptFilter = ^TEncryptFilter; | |
| TEncryptFilter = | |
| object(TFilter) | |
| { Filter which encrypts text going in or out; encrypting twice with the same | |
| key decrypts. Not very sophisticated encryption. } | |
| Key : LongInt; | |
| { Key is used as a Randseed replacement } | |
| constructor Init(Akey : LongInt; ABase : PStream); | |
| { Init with a given key } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| const | |
| MaxStack = 4096; { Must match lzwstream.asm declaration! } | |
| type | |
| PLZWTables = ^TLZWTables; | |
| TLZWTables = | |
| record | |
| Collision : array[0..MaxStack-1] of Byte; { Hash table entries } | |
| PrefixTable : array[0..MaxStack-1] of Word; { Code for preceding stringf } | |
| SuffixTable : array[0..MaxStack-1] of Byte; { Code for current character } | |
| ChildTable : array[0..MaxStack-1] of Word; { Next duplicate in collision | |
| list. } | |
| CharStack : array[0..MaxStack-1] of Byte; { Decompression stack } | |
| StackPtr : Word; { Decompression stack depth } | |
| Prefix : Word; { Previous code string } | |
| TableUsed : Word; { # string table entries used } | |
| InputPos : Word; { Index in input buffer } | |
| OutputPos : Word; { Index in output buffer } | |
| LastHit : Word; { Last empty slot in collision | |
| table. } | |
| CodeBuf : Word; | |
| SaveIP : Word; | |
| SaveAX : Word; | |
| SaveCX : Word; | |
| SaveDX : Word; | |
| NotFound : Byte; { Character combination found | |
| flag. } | |
| end; | |
| PLZWFilter = ^TLZWFilter; | |
| TLZWFilter = | |
| object(TFilter) | |
| Mode : Word; { Either stOpenRead or stOpenWrite. } | |
| Size, { The size of the expanded stream. } | |
| Position : LongInt; { The current position in the expanded stream } | |
| Tables : PLZWTables; { Tables holding the compressor state. } | |
| constructor Init(ABase : PStream; AMode : TOpenMode); | |
| { Create new compressor stream, to use ABase as the source/destination | |
| for data. AMode must be stOpenRead or stOpenWrite. } | |
| destructor Done; virtual; | |
| { Flushes all data to the stream, and writes the uncompressed | |
| filesize to the head of it before calling TFilter.done. } | |
| procedure Flush; virtual; | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| { Seek is not supported at all in Write mode. In Read mode, it is | |
| slow for seeking forwards, and very slow for seeking backwards: | |
| it rewinds the file to the start and readforward from there. } | |
| procedure Truncate; virtual; | |
| { Truncate is not supported in either mode, and always causes a | |
| call to Error. } | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| type | |
| PTextFilter = ^TTextFilter; | |
| TTextFilter = | |
| object(TFilter) | |
| { A filter to provide ReadLn/WriteLn interface to a stream. First | |
| open the stream and position it, then pass it to this filter; | |
| then Reset, Rewrite, or Append the Textfile variable, and do all | |
| reads and writes to it; they'll go to the stream through a TFDD. | |
| You can also assign the stream to any other text variable using | |
| the #AssignStream# method. } | |
| TextFile : Text; | |
| { A fake text file to use with Read(ln)/Write(ln). } | |
| TextPtr : ^text; | |
| { A pointer to the text file used by the filter. Initialized | |
| to point to TextFile, but #AssignStream# will change TextPtr. } | |
| constructor Init(ABase : PStream; AName : String); | |
| { Initialize the interface to ABase; stores AName in the name field of | |
| #Textfile#. AName isn't used beyond this, but may be helpful | |
| if you choose to watch the TextFile field in the debugger. } | |
| destructor Done; virtual; | |
| { Flushes the text file, then closes and disposes of the base stream. } | |
| procedure AssignStream(var NewText:text; AName : String); | |
| { Close the currently assigned text file, and assign a new one. | |
| As with #Init#, the name is stored in NewText, but is not otherwise | |
| used.} | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Flush; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| type | |
| PLogFilter = ^TLogFilter; | |
| TLogFilter = | |
| object(TFilter) | |
| { A filter to log activity on a text file. } | |
| LogList : ^Text; { A pointer to the first logged file } | |
| constructor init(ABase:PStream); | |
| { Initializes filter, but doesn't start logging anything } | |
| destructor Done; virtual; | |
| { Stops logging all files, and closes & disposes of the base stream } | |
| procedure Log(var F : Text); | |
| { Logs all input and output to F to the stream. You must do the Assign to | |
| F first, and not do another Assign without closing F. } | |
| function Unlog(var F : Text) : Boolean; | |
| { Stops logging of F. Called automatically if file is closed. Returns | |
| false and does nothing on error. } | |
| end; | |
| TBit = 0..1; { A single bit } | |
| PBitFilter = ^TBitFilter; | |
| TBitFilter = | |
| object(TFilter) | |
| BitPos : ShortInt; | |
| { Position of stream relative to base file. Negative values signal | |
| that the buffer is unchanged from the file, positive values signal | |
| that the file needs to be updated. Zero signals an empty buffer. } | |
| Mask : Byte; { Mask to extract next bit from buffer } | |
| Buffer : Byte; { Buffer of next 8 bits from stream } | |
| AtEnd : Boolean; { Flag to signal that we're at the end | |
| of the base, and we shouldn't read | |
| it. Bases that change in length should | |
| set this to false. } | |
| constructor Init(ABase : PStream); | |
| procedure Flush; virtual; { Flush buffer to stream } | |
| procedure Seek(Pos : LongInt); virtual; { Seek to bit at start of | |
| pos byte. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| function GetBit : TBit; { Get next bit from stream } | |
| function GetBits(Count : Byte) : LongInt; { Get up to 32 bits } | |
| procedure ReadBits(var Buf; Count : LongInt); { Read bits from stream } | |
| procedure PutBit(ABit : TBit); { Put one bit to stream } | |
| procedure PutBits(ABits : LongInt; Count : Byte); { Put up to 32 bits, | |
| low bits first. } | |
| procedure WriteBits(var Buf; Count : LongInt); { Write count bits to stream } | |
| procedure SeekBit(Pos : LongInt); { Seek to particular bit } | |
| function GetBitPos : LongInt; | |
| procedure CopyBits(var S : TBitFilter; Count : LongInt); { Copy bits from S } | |
| procedure ByteAlign; { Seek forward to next byte boundary. } | |
| procedure PrepareBuffer(ForRead : Boolean); | |
| { Internal method to assure that buffer is valid } | |
| end; | |
| PDupFilter = ^TDupFilter; | |
| TDupFilter = | |
| object(TFilter) { Duplicates output, confirms matching input } | |
| Base2 : PStream; | |
| { Pointer to the second base. } | |
| Startofs2 : LongInt; | |
| { The offset of the start of the filter in the second base. } | |
| constructor Init(ABase, ABase2 : PStream); | |
| { Initialize the filter with the given bases. } | |
| destructor Done; virtual; | |
| { Flush filter, then dispose of both bases. } | |
| function MisMatch(var buf1,buf2; count:word):word; virtual; | |
| { Checks for a mismatch between the two buffers. Returns | |
| the byte number of the mismatch (1 based), or 0 if they | |
| test equal. This default method checks for an exact match. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| procedure Flush; virtual; | |
| function CheckStatus : Boolean; virtual; | |
| { Return true if status is stOK. | |
| If status is stOK, but base is not, then reset the base. This is a poor | |
| substitute for a virtual Reset method. } | |
| procedure CheckBase2; | |
| { Check 2nd base stream for error, and copy status using own Error method. } | |
| end; | |
| PConcatFilter = ^TConcatFilter; | |
| TConcatFilter = | |
| object(TFilter) | |
| { A filter which acts to concatenate two streams (or parts of streams) | |
| so that they appear as one.} | |
| Base2 : PStream; | |
| { Pointer to the second base. This one logically follows the first.} | |
| Startofs2 : LongInt; | |
| { The offset of the start of the filter in the second base. } | |
| Position : Longint; | |
| { The current position of the filter. The corresponding | |
| base stream is kept synchronized with this } | |
| Base1Size : Longint; | |
| { This is used a lot to determine switching. } | |
| constructor Init(ABase, ABase2 : PStream); { Initialize the filter with the given bases. } | |
| destructor Done; virtual; | |
| { Flush filter, then dispose of both bases. } | |
| function GetPos:longint; virtual; | |
| function GetSize:longint; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| procedure Flush; virtual; | |
| { These methods work directly on Base until its size | |
| is reached, then switch over to Base2. Base will *never* grow | |
| from the size at stream initialization. } | |
| function CheckStatus : Boolean; virtual; | |
| procedure CheckBase2; | |
| { Check 2nd base stream for error, and copy status using own Error method. } | |
| end; | |
| PLimitFilter = ^TLimitFilter; | |
| TLimitFilter = | |
| object(TFilter) | |
| { Limits all access to the bytes between LoLimit and HiLimit. } | |
| LoLimit,HiLimit : longint; | |
| { The lower and upper limit points. These are in the TFilter | |
| scale, i.e. relative to #TFilter.Base#. } | |
| constructor init(ABase:PStream;ALoLimit,AHiLimit:longint); | |
| { Does the usual init, sets the limits, then does a Seek to ALoLimit | |
| if it is non-zero. } | |
| function GetSize:longint; virtual; | |
| { Returns the smaller of HiLimit and the #TFilter.GetSize# value. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PLoopFilter = ^TLoopFilter; | |
| TLoopFilter = | |
| object(TLimitFilter) | |
| { Moves all access to the bytes between LoLimit and HiLimit. } | |
| function GetSize:longint; virtual; | |
| { Returns the smaller of the size between the limits, or from | |
| the low limit to the end of the base } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PReverseFilter = ^TReverseFilter; | |
| TReverseFilter = | |
| object(TFilter) | |
| { Reads and writes the base in reverse order. } | |
| ReverseBlocks : Boolean; { Whether to reverse the bytes within | |
| a Read/Write block } | |
| constructor init(ABase:PStream; AReverseBlocks:boolean); | |
| { Standard initialization } | |
| function GetPos:longint; virtual; | |
| { Returns the position in bytes from the end of the base } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| { See #Write#. } | |
| procedure Write(var Buf; Count : Word); virtual; | |
| { These methods read/write the block of bytes just previous to | |
| the current base file pointer. The bytes themselves are | |
| reversed if #ReverseBlocks# is true. } | |
| procedure Seek(Pos : LongInt); virtual; | |
| { Does the Seek in the reversed byte order, i.e. count from the | |
| end of the stream } | |
| procedure Truncate; virtual; | |
| { Triggers an #stUnsupported# error. } | |
| end; | |
| procedure ReverseBytes(var Buf; Count : Word); | |
| { Reverses the order of the bytes in the buffer } | |
| type | |
| PSequential = ^TSequential; | |
| TSequential = | |
| object(TFilter) { Filter for sequential access only } | |
| procedure Seek(pos:longint); virtual;{ Signals stUnsupported if a Seek is attempted } | |
| end; | |
| PChksumFilter = ^TChksumFilter; | |
| TChksumFilter = | |
| object(TSequential) { Calculates 16 bit checksum of | |
| bytes read/written. } | |
| Chksum : word; | |
| constructor Init(ABase : PStream;AChksum:word); | |
| { Initialize the filter with the given base and starting checksum. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PCRC16Filter = ^TCRC16Filter; | |
| TCRC16Filter = | |
| object(TSequential) { Calculates XMODEM style 16 bit CRC } | |
| CRC16 : word; | |
| constructor Init(ABase : PStream;ACRC16:word); | |
| { Initialize the filter with the given base and starting CRC. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PCRCARCFilter = ^TCRCARCFilter; | |
| TCRCARCFilter = | |
| object(TSequential) { Calculates ARC-style 16 bit CRC } | |
| CRCARC : word; | |
| constructor Init(ABase : PStream;ACRCARC:word); | |
| { Initialize the filter with the given base and starting CRC. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PCRC32Filter = ^TCRC32Filter; | |
| TCRC32Filter = | |
| object(TSequential) { Calculates PKZIP and ZModem style 32 bit CRC } | |
| CRC32 : longint; | |
| constructor Init(ABase : PStream;ACRC32:longint); | |
| { Initialize the filter with the given base and starting CRC. } | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PNulStream = ^TNulStream; | |
| TNulStream = | |
| object(TStream) | |
| Position : LongInt; { The current position for the stream. } | |
| Value : Byte; { The value returned on reads. } | |
| constructor Init(AValue : Byte); | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| Pbyte_array = ^Tbyte_array; | |
| Tbyte_array = array[0..65520] of Byte; { Type used as a buffer. } | |
| PRAMStream = ^TRAMStream; | |
| TRAMStream = | |
| object(TStream) | |
| Position : Word; { The current position for the stream. } | |
| Size : Word; { The current size of the stream. } | |
| Alloc : Word; { The size of the allocated block of memory. } | |
| Buffer : Pbyte_array; { Points to the stream data. } | |
| OwnMem : Boolean; { Whether Done should dispose of data.} | |
| constructor Init(Asize : Word); | |
| { Attempt to initialize the stream to a block size of Asize; | |
| initial stream size and position are 0. } | |
| constructor UseBuf(ABuffer : Pointer; Asize : Word); | |
| { Initialize the stream using the specified buffer. OwnMem is set | |
| to false, so the buffer won't be disposed of. Initial position is 0, | |
| size is Asize. } | |
| destructor Done; virtual; | |
| { Dispose of the stream. } | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| end; | |
| PEMSStream2 = ^TEMSStream2; | |
| TEMSStream2 = object(TEMSStream) | |
| destructor done; virtual; | |
| { Corrects bug in #TEMSStream# that leaves #EMSCurPage# wrong when | |
| done is called } | |
| end; | |
| PXMSStream = ^TXMSStream; | |
| TXMSStream = | |
| object(TStream) | |
| Handle : Word; { XMS handle } | |
| BlocksUsed : Word; { Number of 1K blocks used. Always allocates | |
| at least one byte more than Size. } | |
| Size : LongInt; { The current size of the stream } | |
| Position : LongInt; { Current position } | |
| constructor Init(MinSize,MaxSize:longint); | |
| destructor Done; virtual; | |
| function GetPos : LongInt; virtual; | |
| function GetSize : LongInt; virtual; | |
| procedure Read(var Buf; Count : Word); virtual; | |
| procedure Seek(Pos : LongInt); virtual; | |
| procedure Truncate; virtual; | |
| procedure Write(var Buf; Count : Word); virtual; | |
| procedure NewBlock; { Internal method to allocate a block } | |
| procedure FreeBlock; { Internal method to free one block } | |
| end; | |
| function xms_MemAvail : Longint; | |
| { Returns total of available XMS bytes. } | |
| function xms_MaxAvail : Longint; | |
| { Returns size of largest available XMS block in bytes. } | |
| function ems_MemAvail : Longint; | |
| { Returns total of available EMS in bytes. } | |
| function ems_MaxAvail : Longint; | |
| { Returns size of largest available EMS block in bytes. } | |
| const | |
| TempEnvVar : String[12] = 'TEMP'; | |
| { The name of an environment variable holding a directory list | |
| where #TTempBufStream# should go looking for disk space. } | |
| function disk_MemAvail : Longint; | |
| { Returns total of available disk space for temp streams, from the | |
| list specified by #TempEnvVar#. } | |
| function disk_MaxAvail : Longint; | |
| { Returns maximum available block of disk space for temp streams, | |
| from the list specified by #TempEnvVar#. } | |
| type | |
| PNamedBufStream = ^TNamedBufStream; | |
| TNamedBufStream = | |
| object(TBufStream) | |
| { A simple descendant of TBufStream which knows its own name.} | |
| {$ifdef windows} | |
| filename : PChar; | |
| {$else} | |
| Filename : PString; | |
| {$endif} | |
| { The name of the stream. } | |
| constructor Init(Name : FNameStr; Mode : TOpenMode; ABufSize : Word); | |
| { Open the file with the given name, and save the name. } | |
| destructor Done; virtual; | |
| { Close the file. } | |
| end; | |
| PTempBufStream = ^TTempBufStream; | |
| TTempBufStream = | |
| object(TNamedBufStream) | |
| { A temporary buffered file stream, which deletes itself when done. | |
| It's allocated on one of the directories specified by #TempEnvVar#.} | |
| constructor Init(ABufSize : Word;InitSize,MaxSize : Longint); | |
| { Create a temporary file with a unique name, in the directory | |
| pointed to by the environment varable named in #TempEnvVar# or in | |
| the current directory, open it in read/write mode, and try to grow | |
| it to InitSize bytes. } | |
| destructor Done; virtual; | |
| { Close and delete the temporary file. } | |
| end; | |
| TStreamType = (NoStream, RAMStream, EMSStream, XMSStream, FileStream); | |
| { The type of stream that a tempstream might be. } | |
| const | |
| NumTypes = Ord(FileStream); | |
| type | |
| TStreamRanking = array[1..NumTypes] of TStreamType; | |
| { A ranking of preference for a type of stream, from most to least preferred } | |
| TAllocator = function (InitSize, MaxSize : LongInt; | |
| Preference : TStreamRanking) : PStream; | |
| { This is a declaration just like the Streams.TempStream function.} | |
| PWorkStream = ^TWorkStream; | |
| TWorkStream = | |
| object(TFilter) | |
| { This is a stream type that grows as you write to it by allocating new | |
| blocks according to a specified strategy. Blocks may be of mixed | |
| types. It's a descendant of a filter, but it manages its own base. } | |
| Allocate : TAllocator; | |
| BlockMin, { These fields are passed to Allocate } | |
| BlockMax : longint; | |
| Preference : TStreamRanking; | |
| BlockStart: longint; { The offset in the stream where the | |
| last block starts. } | |
| constructor init(Allocator:TAllocator;ABlockmin,ABlockMax:Longint; | |
| APreference : TStreamRanking); | |
| { ABlockmin to APreference are passed to the allocator to allocate | |
| a new block whenever the current one gives a write error. | |
| The TWorkStream will never try to write a single block that crosses | |
| the ABlockMax boundary, so tests within the stream can be simple.} | |
| procedure write(var Buf; Count:Word); virtual; | |
| { The write procedure checks whether the write would make the | |
| current block grow too large; if so, it splits up the write. } | |
| end; | |
| const | |
| BufSize : Word = 2048; { Buffer size if buffered stream is used. } | |
| const ForSpeed : TStreamRanking = (RAMStream, EMSStream, XMSStream, FileStream); | |
| { Streams ordered for speed } | |
| const ForSize : TStreamRanking = (FileStream, EMSStream, XMSStream, RAMStream); | |
| { Streams ordered for low impact on the heap } | |
| const ForSizeInMem : TStreamRanking = (EMSStream, XMSStream, RAMStream, NoStream); | |
| { Streams in memory only, ordered as #ForSize#. } | |
| const ForOverlays : TStreamRanking = (EMSStream, XMSStream, FileStream, NoStream); | |
| { Streams ordered for speed, but never in RAM. } | |
| function TempStream(InitSize, MaxSize : LongInt; | |
| Preference : TStreamRanking) : PStream; | |
| { This procedure returns a pointer to a temporary stream from a | |
| choice of 3, specified in the Preference array. The first stream | |
| type listed in the Preference array which can be successfully | |
| created with the given sizes will be returned, or Nil if none can | |
| be made. } | |
| function StreamName(S:PStream):String; | |
| { This function returns a string naming the type of S^. It's useful for | |
| debugging programs that use TempStream and TWorkStream. However, | |
| it's for debugging only! It links every single stream type into your | |
| .EXE. } | |
| {$ifdef overlays} | |
| procedure OvrInitStream(S : PStream); | |
| { Copies overlay segment code to S as new segments are loaded, | |
| and does reloads from there. Allows multiple calls, to buffer | |
| different segments on different streams. } | |
| procedure OvrDetachStream(BadS : PStream); | |
| { Makes sure that the overlay system makes no references to BadS. } | |
| procedure OvrDisposeStreams; | |
| { Detaches and disposes of all streams being used by the overlay system } | |
| function OvrSizeNeeded : LongInt; | |
| { Returns the size required to load any segments which still haven't | |
| been loaded to a stream. } | |
| function OvrLoadAll : Boolean; | |
| { Forces all overlay segments to be copied into the stream; if successful | |
| (true) then no more references to the overlay file will be made. } | |
| {$endif windows} | |
| Function UpdateChksum(Initsum: Word; Var InBuf; InLen : Word) : Word; | |
| { Updates the checksum Initsum by adding InLen bytes from InBuf } | |
| Function UpdateCRC16(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| { I believe this is the CRC used by the XModem protocol. The transmitting | |
| end should initialize with zero, UpdateCRC16 for the block, Continue the | |
| UpdateCRC16 for two nulls, and append the result (hi order byte first) to | |
| the transmitted block. The receiver should initialize with zero and | |
| UpdateCRC16 for the received block including the two byte CRC. The | |
| result will be zero (why?) if there were no transmission errors. (I have | |
| not tested this function with an actual XModem implementation, though I | |
| did verify the behavior just described. See TESTCRC.PAS.) } | |
| Function UpdateCRCArc(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| { This function computes the CRC used by SEA's ARC utility. Initialize | |
| with zero. } | |
| Function UpdateCRC32(InitCRC : LongInt; Var InBuf; InLen : Word) : LongInt; | |
| { This function computes the CRC used by PKZIP and Forsberg's ZModem. | |
| Initialize with high-values ($FFFFFFFF), and finish by inverting all bits | |
| (Not). } | |
| Procedure FastCopy(var Src,Dest:TStream;size:longint); | |
| { Like Dest.CopyFrom(Src,size), but tries to use a larger buffer } | |
| implementation | |
| function MinLong(x,y:longint):longint; | |
| begin | |
| if x<y then | |
| MinLong := x | |
| else | |
| MinLong := y; | |
| end; | |
| function MaxLong(x,y:longint):longint; | |
| begin | |
| MaxLong := -MinLong(-x,-y); | |
| end; | |
| function MinWord(x,y:word):word; | |
| begin | |
| if x<y then | |
| MinWord := x | |
| else | |
| MinWord := y; | |
| end; | |
| function MaxWord(x,y:word):word; | |
| begin | |
| MaxWord := -MinWord(-x,-y); | |
| end; | |
| {****** TFilter code *******} | |
| constructor TFilter.Init(ABase : PStream); | |
| begin | |
| TStream.Init; | |
| Base := ABase; | |
| CheckBase; | |
| if Status = stOK then | |
| Startofs := Base^.GetPos; | |
| OwnsBase := true; | |
| end; | |
| destructor TFilter.Done; | |
| begin | |
| if Base <> nil then | |
| begin | |
| Flush; | |
| if OwnsBase then | |
| Dispose(Base, Done); | |
| end; | |
| TStream.Done; | |
| end; | |
| function TFilter.GetPos : LongInt; | |
| begin | |
| if CheckStatus then | |
| begin | |
| GetPos := Base^.GetPos-Startofs; | |
| CheckBase; | |
| end; | |
| end; | |
| function TFilter.GetSize : LongInt; | |
| begin | |
| if CheckStatus then | |
| begin | |
| GetSize := Base^.GetSize-Startofs; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TFilter.Read(var Buf; Count : Word); | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base^.Read(Buf, Count); | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TFilter.Seek(Pos : LongInt); | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base^.Seek(Pos+Startofs); | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TFilter.Truncate; | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base^.Truncate; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TFilter.Write(var Buf; Count : Word); | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base^.Write(Buf, Count); | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TFilter.Flush; | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base^.Flush; | |
| CheckBase; | |
| end; | |
| end; | |
| function TFilter.CheckStatus : Boolean; | |
| begin | |
| if (Status = stOK) and (Base^.Status <> stOK) then | |
| Base^.Reset; | |
| CheckStatus := Status = stOK; | |
| end; | |
| procedure TFilter.CheckBase; | |
| begin | |
| if Base^.Status <> stOK then | |
| Error(stBaseError, Base^.Status); | |
| end; | |
| constructor TEncryptFilter.Init(Akey : LongInt; ABase : PStream); | |
| begin | |
| TFilter.Init(ABase); | |
| Key := Akey; | |
| end; | |
| procedure TEncryptFilter.Read(var Buf; Count : Word); | |
| var | |
| i : Word; | |
| SaveSeed : LongInt; | |
| Bytes : Tbyte_array absolute Buf; | |
| begin | |
| SaveSeed := RandSeed; | |
| RandSeed := Key; | |
| TFilter.Read(Buf, Count); | |
| for i := 0 to Count-1 do | |
| Bytes[i] := Bytes[i] xor Random(256); | |
| Key := RandSeed; | |
| RandSeed := SaveSeed; | |
| end; | |
| procedure CycleKey(Key, Cycles : LongInt); | |
| { For cycles > 0, mimics cycles calls to the TP random number generator. | |
| For cycles < 0, backs it up the given number of calls. } | |
| var | |
| i : LongInt; | |
| Junk : Integer; | |
| SaveSeed : LongInt; | |
| begin | |
| if Cycles > 0 then | |
| begin | |
| SaveSeed := RandSeed; | |
| RandSeed := Key; | |
| for i := 1 to Cycles do | |
| Junk := Random(0); | |
| Key := RandSeed; | |
| RandSeed := SaveSeed; | |
| end | |
| else | |
| for i := -1 downto Cycles do | |
| Key := (Key-1)*(-649090867); | |
| end; | |
| procedure TEncryptFilter.Seek(Pos : LongInt); | |
| var | |
| OldPos : LongInt; | |
| begin | |
| OldPos := GetPos; | |
| TFilter.Seek(Pos); | |
| CycleKey(Key, Pos-OldPos); | |
| end; | |
| procedure TEncryptFilter.Write(var Buf; Count : Word); | |
| var | |
| i : Word; | |
| SaveSeed : LongInt; | |
| BufPtr : Pointer; | |
| BufPtrOffset : Word absolute BufPtr; | |
| Buffer : array[0..255] of Byte; | |
| begin | |
| SaveSeed := RandSeed; | |
| RandSeed := Key; | |
| BufPtr := @Buf; | |
| while Count > 256 do | |
| begin | |
| Move(BufPtr^, Buffer, 256); | |
| for i := 0 to 255 do | |
| Buffer[i] := Buffer[i] xor Random(256); | |
| TFilter.Write(Buffer, 256); | |
| Dec(Count, 256); | |
| Inc(BufPtrOffset, 256); | |
| end; | |
| Move(BufPtr^, Buffer, Count); | |
| for i := 0 to Count-1 do | |
| Buffer[i] := Buffer[i] xor Random(256); | |
| TFilter.Write(Buffer, Count); | |
| Key := RandSeed; | |
| RandSeed := SaveSeed; | |
| end; | |
| { ******* LZW code ******* } | |
| {$L LZWSTREAM.OBJ} | |
| procedure Initialise(Tables : PLZWTables); External; | |
| function PutSignature(Tables : PLZWTables) : Boolean; External; | |
| function Crunch(InBufSize, OutBufSize : Word; | |
| var InBuffer, OutBuffer; | |
| Tables : PLZWTables) : Pointer; External; | |
| { Crunch some more text. Stops when Inbufsize bytes are used up, or | |
| output buffer is full. Returns bytes used in segment, bytes written | |
| in offset of result } | |
| function FlushLZW(var OutBuffer; | |
| Tables : PLZWTables) : Word; External; | |
| { Flush the remaining characters to signal EOF. Needs space for up to | |
| 3 characters. } | |
| function GetSignature(var InBuffer, Dummy; | |
| Tables : PLZWTables) : Boolean; External; | |
| { Initializes for reading, and checks for 'LZ' signature in start of compressed | |
| code. Inbuffer must contain at least 3 bytes. Dummy is just there to put the | |
| Inbuffer in the right spot } | |
| function Uncrunch(InBufSize, OutBufSize : Word; | |
| var InBuffer, OutBuffer; | |
| Tables : PLZWTables) : Pointer; External; | |
| { Uncrunch some text. Will stop when it has done Outbufsize worth or has | |
| exhausted Inbufsize worth. Returns bytes used in segment, bytes written | |
| in offset of result } | |
| constructor TLZWFilter.Init(ABase : PStream; AMode : TOpenMode); | |
| { Create new compressor stream, to use ABase as the source/destination | |
| for data. Mode must be stOpenRead or stOpenWrite. } | |
| var | |
| Buffer : array[1..3] of Byte; | |
| Info : Integer; | |
| begin | |
| Info := stBadMode; | |
| if (AMode = stOpenRead) or (AMode = stOpenWrite) then | |
| begin | |
| Info := stStreamFail; | |
| if TFilter.Init(ABase) then | |
| begin | |
| if Status = stOK then | |
| begin | |
| Info := stMemError; | |
| Startofs := Base^.GetPos; | |
| Position := 0; | |
| Mode := AMode; | |
| if MaxAvail >= SizeOf(TLZWTables) then | |
| begin | |
| Info := stSigError; | |
| GetMem(Tables, SizeOf(TLZWTables)); | |
| if Tables <> nil then { !1.6} | |
| begin | |
| Initialise(Tables); | |
| if Mode = stOpenRead then | |
| begin | |
| Base^.Read(Size, SizeOf(Size)); | |
| Base^.Read(Buffer, 3); | |
| CheckBase; | |
| if GetSignature(Buffer, Buffer, Tables) then | |
| Exit; { Successfully opened for reading } | |
| end | |
| else if Mode = stOpenWrite then | |
| begin | |
| Size := 0; | |
| Base^.Write(Size, SizeOf(Size)); { Put a place holder } | |
| CheckBase; | |
| if PutSignature(Tables) then | |
| Exit; { Successful construction for writing! } | |
| end; | |
| end; | |
| end; | |
| end; | |
| end; | |
| end; | |
| Error(stInitError, Info); | |
| end; | |
| destructor TLZWFilter.Done; | |
| begin | |
| Flush; | |
| FreeMem(Tables, SizeOf(TLZWTables)); | |
| TFilter.Done; | |
| end; | |
| procedure TLZWFilter.Write(var Buf; Count : Word); | |
| var | |
| Inbuf : array[0..65520] of Byte absolute Buf; | |
| Outbuf : array[0..255] of Byte; | |
| Inptr : Word; | |
| Sizes : record | |
| OutSize, UsedSize : Word; | |
| end; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if Mode <> stOpenWrite then | |
| Error(stBadMode, Mode); | |
| Inptr := 0; | |
| repeat | |
| Pointer(Sizes) := Crunch(Count, SizeOf(Outbuf), | |
| Inbuf[Inptr], Outbuf, Tables); | |
| with Sizes do | |
| begin | |
| Base^.Write(Outbuf, OutSize); | |
| Dec(Count, UsedSize); | |
| Inc(Inptr, UsedSize); | |
| Inc(Size, UsedSize); | |
| Inc(Position, UsedSize); | |
| end; | |
| until Count = 0; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TLZWFilter.Flush; | |
| var | |
| Outbuf : array[0..255] of Byte; | |
| Sizes : record | |
| OutSize, UsedSize : Word; | |
| end; | |
| Pos : LongInt; | |
| extra : word; | |
| zero : byte; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if Mode = stOpenWrite then | |
| begin | |
| { Changes here in 1.6 } | |
| extra := 0; | |
| zero := 0; | |
| repeat | |
| Pointer(Sizes) := Crunch(1, Sizeof(Outbuf), zero, Outbuf, Tables); | |
| with Sizes do | |
| if OutSize > 0 then | |
| begin | |
| inc(extra,outsize); | |
| Base^.Write(Outbuf, Outsize); | |
| end; | |
| until extra > 4; | |
| Pos := Base^.GetPos; | |
| Base^.Seek(Startofs); | |
| Base^.Write(Size, SizeOf(Size)); | |
| Base^.Seek(Pos); | |
| end; | |
| Base^.Flush; | |
| Mode := 0; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TLZWFilter.Read(var Buf; Count : Word); | |
| var | |
| Outbuf : array[0..65520] of Byte absolute Buf; | |
| Inbuf : array[0..255] of Byte; | |
| OutPtr : Word; | |
| BlockSize : Word; | |
| Sizes : record | |
| OutSize, UsedSize : Word; | |
| end; | |
| BytesLeft : LongInt; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if Mode <> stOpenRead then | |
| Error(stBadMode, Mode); | |
| OutPtr := 0; | |
| BlockSize := SizeOf(Inbuf); | |
| with Base^ do | |
| BytesLeft := GetSize-GetPos; | |
| if Position+Count > Size then | |
| begin | |
| Error(stReaderror, 0); | |
| FillChar(Buf, Count, 0); | |
| Exit; | |
| end; | |
| while Count > 0 do | |
| begin | |
| if BytesLeft < BlockSize then | |
| BlockSize := BytesLeft; | |
| Base^.Read(Inbuf, BlockSize); | |
| Pointer(Sizes) := Uncrunch(BlockSize, Count, Inbuf, | |
| Outbuf[OutPtr], Tables); | |
| with Sizes do | |
| begin | |
| if OutSize = 0 then | |
| begin | |
| Error(stReaderror, 0); | |
| FillChar(Outbuf[OutPtr], Count, 0); | |
| Exit; | |
| end; | |
| Dec(BytesLeft, UsedSize); | |
| Inc(Position, OutSize); | |
| Dec(Count, OutSize); | |
| Inc(OutPtr, OutSize); | |
| if UsedSize < BlockSize then | |
| with Base^ do { seek back to the first unused byte } | |
| Seek(GetPos-(BlockSize-UsedSize)); | |
| end; | |
| end; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TLZWFilter.Seek(Pos : LongInt); | |
| var | |
| Buf : array[0..255] of Byte; | |
| Bytes : Word; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if Mode <> stOpenRead then | |
| begin | |
| Error(stBadMode, Mode); | |
| Exit; | |
| end; | |
| if Pos < Position then | |
| begin | |
| Base^.Seek(Startofs); | |
| FreeMem(Tables, SizeOf(TLZWTables)); | |
| TLZWFilter.Init(Base, Mode); { Re-initialize everything. Will this cause | |
| bugs in descendents? } | |
| end; | |
| while Pos > Position do | |
| begin | |
| if Pos-Position > SizeOf(Buf) then | |
| Bytes := SizeOf(Buf) | |
| else | |
| Bytes := Pos-Position; | |
| Read(Buf, Bytes); | |
| end; | |
| end; | |
| end; | |
| procedure TLZWFilter.Truncate; | |
| begin | |
| Error(stBadMode, Mode); | |
| end; | |
| function TLZWFilter.GetPos; | |
| begin | |
| GetPos := Position; | |
| end; | |
| function TLZWFilter.GetSize; | |
| begin | |
| GetSize := Size; | |
| end; | |
| { ***** Text Filter Code ******* } | |
| { These declarations are used both by TTextFilter and TLogFilter } | |
| type | |
| TFDDfunc = function(var F : Text) : Integer; | |
| PStreamTextRec = ^StreamTextRec; | |
| PSaveText = ^TSaveText; | |
| TSaveText = | |
| record { Used when logging for original data values } | |
| OpenFunc, | |
| InOutFunc, | |
| FlushFunc, | |
| CloseFunc : TFDDfunc; | |
| S : PLogFilter; | |
| SaveData : PSaveText; | |
| Next : PStreamTextRec; | |
| Data : array[13..16] of Byte; | |
| end; | |
| StreamTextRec = | |
| record | |
| Handle : Word; | |
| Mode : Word; | |
| BufSize : Word; | |
| private : Word; | |
| BufPos : Word; | |
| BufEnd : Word; | |
| BufPtr : Pbyte_array; | |
| OpenFunc, | |
| InOutFunc, | |
| FlushFunc, | |
| CloseFunc : TFDDfunc; | |
| S : PFilter; { This is a TTextFilter or a TLogFilter } | |
| SaveData : PSaveText; | |
| Next : PStreamTextRec; | |
| OtherData : array[13..16] of Byte; | |
| Name : array[0..79] of Char; | |
| Buffer : array[0..127] of Byte; | |
| end; | |
| function XLATstatus(var S:TStream):integer; | |
| const | |
| TextErrors : array[0..6] of integer = (0,5,5,100,101,212,212); | |
| var | |
| status : integer; | |
| begin | |
| status := S.status; | |
| if (status = stBaseError) or (status = stBase2Error) then | |
| status := S.errorinfo; | |
| if (-6 <= status) and (status <= 0) then | |
| XLATstatus := TextErrors[-status] | |
| else | |
| XLATstatus := 5; | |
| end; | |
| function TextIn(var F : Text) : Integer; Far; | |
| var | |
| savemode : word; | |
| begin | |
| with StreamTextRec(F), S^ do | |
| begin | |
| if Status = 0 then | |
| begin | |
| savemode := mode; | |
| mode := fmClosed; { This stops infinite loop } | |
| if GetSize-GetPos > BufSize then | |
| begin | |
| Read(BufPtr^, BufSize); | |
| BufEnd := BufSize; | |
| end | |
| else | |
| begin | |
| BufEnd := GetSize-GetPos; | |
| if BufEnd > 0 then | |
| Read(BufPtr^, BufEnd); | |
| end; | |
| BufPos := 0; | |
| mode := savemode; | |
| end; | |
| TextIn := XLATStatus(S^); | |
| end; | |
| end; | |
| function TextOut(var F : Text) : Integer; Far; | |
| var | |
| savemode : word; | |
| begin | |
| with StreamTextRec(F), S^ do | |
| begin | |
| if Status = 0 then | |
| begin | |
| savemode := mode; | |
| mode := fmClosed; | |
| Write(BufPtr^, BufPos); | |
| mode := savemode; | |
| BufPos := 0; | |
| end; | |
| TextOut := XLATStatus(S^); | |
| end; | |
| end; | |
| function TextInFlush(var F : Text) : Integer; Far; | |
| begin | |
| TextInFlush := 0; { 1.3A bug fix } | |
| end; | |
| function TextOutFlush(var F : Text) : Integer; Far; | |
| begin | |
| TextOutFlush := 0; { 1.6 change from TextOut(F); } | |
| end; | |
| function TextClose(var F : Text) : Integer; Far; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| S^.Flush; | |
| TextClose := XLATStatus(S^); | |
| end; | |
| end; | |
| function TextOpen(var F : Text) : Integer; Far; | |
| var | |
| saveMode : word; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| case Mode of | |
| fmInOut : | |
| begin | |
| Mode := fmClosed; | |
| S^.Seek(S^.GetSize); | |
| Mode := fmOutput; | |
| end; | |
| fmInput,fmOutput : | |
| begin | |
| saveMode := Mode; | |
| Mode := fmClosed; | |
| S^.Seek(0); | |
| Mode := saveMode; | |
| end; | |
| end; | |
| case Mode of | |
| fmInput : begin | |
| InOutFunc := TextIn; | |
| FlushFunc := TextInFlush; | |
| end; | |
| fmOutput : begin | |
| InOutFunc := TextOut; | |
| FlushFunc := TextOutFlush; | |
| end; | |
| end; | |
| TextOpen := XLATStatus(S^); | |
| end; | |
| end; | |
| constructor TTextFilter.Init(ABase : PStream; AName : String); | |
| begin | |
| if not TFilter.Init(ABase) then | |
| Fail; | |
| TextPtr := nil; | |
| AssignStream(TextFile,AName); | |
| end; | |
| destructor TTextFilter.Done; | |
| begin | |
| if StreamTextRec(TextPtr^).Mode <> fmClosed then | |
| Close(Textptr^); | |
| TFilter.Done; | |
| end; | |
| procedure TTextFilter.AssignStream(var NewText:text;AName:string); | |
| begin | |
| if (TextPtr <> nil) and (StreamTextRec(TextPtr^).Mode <> fmClosed) then | |
| Close(TextPtr^); | |
| with StreamTextRec(NewText) do | |
| begin | |
| Mode := fmClosed; | |
| BufSize := SizeOf(Buffer); | |
| BufPtr := PByte_Array(@Buffer); | |
| OpenFunc := TextOpen; | |
| CloseFunc := TextClose; | |
| AName := Copy(AName, 1, 79); | |
| Move(AName[1], Name, Length(AName)); | |
| Name[Length(AName)] := #0; | |
| S := @Self; | |
| end; | |
| TextPtr := @NewText; | |
| end; | |
| function TTextFilter.GetPos : LongInt; | |
| var | |
| result : longint; | |
| begin | |
| result := TFilter.GetPos; | |
| with StreamTextRec(Textptr^) do | |
| case Mode of | |
| fmInput : result := result - (BufEnd - BufPos); | |
| fmOutput : result := result + (BufPos); | |
| end; | |
| GetPos := Result; | |
| end; | |
| function TTextFilter.GetSize : LongInt; | |
| begin | |
| if StreamTextRec(Textptr^).Mode <> fmClosed then | |
| System.Flush(TextPtr^); | |
| GetSize := TFilter.GetSize; | |
| end; | |
| procedure TTextFilter.Flush; | |
| begin | |
| with StreamTextRec(TextPtr^) do | |
| begin | |
| case Mode of | |
| fmOutput : system.flush(TextPtr^); | |
| fmInput : | |
| begin | |
| TFilter.Seek(TFilter.GetPos - BufEnd + BufPos); | |
| BufPos := 0; | |
| BufEnd := 0; | |
| end; | |
| end; | |
| end; | |
| TFilter.Flush; | |
| end; | |
| procedure TTextFilter.Read(var Buf; Count : Word); | |
| var | |
| outbuf : Tbyte_array absolute buf; | |
| size : word; | |
| begin | |
| with StreamTextRec(TextPtr^) do | |
| begin | |
| if mode <> fmInput then { This means we've been called by the | |
| TFDD, or while writing to the text file, | |
| or the text file is closed } | |
| begin | |
| if mode = fmOutput then | |
| Flush; | |
| TFilter.Read(Buf,Count) | |
| end | |
| else | |
| begin | |
| size := minword(Count, BufEnd-BufPos); | |
| move(bufptr^[BufPos],outbuf,size); | |
| dec(count,size); | |
| if count > 0 then | |
| begin | |
| bufpos := 0; | |
| bufend := 0; | |
| TFilter.Read(outbuf[size],count); | |
| end | |
| else | |
| inc(bufpos,size); | |
| end; | |
| end; | |
| end; | |
| procedure TTextFilter.Seek(Pos : LongInt); | |
| var | |
| basepos : longint; | |
| begin | |
| with StreamTextRec(TextPtr^) do | |
| begin | |
| basepos := TFilter.GetPos; | |
| case Mode of | |
| fmInput : begin | |
| if (basepos - bufend <= pos) and (pos < basepos) then | |
| bufpos := pos-(basepos-bufend) | |
| else | |
| begin | |
| Flush; | |
| TFilter.Seek(Pos); | |
| end; | |
| end; | |
| fmOutput : begin | |
| if (basepos <= pos) and (pos < basepos + bufsize) then | |
| bufpos := pos-basepos | |
| else | |
| begin | |
| Flush; | |
| TFilter.Seek(Pos); | |
| end; | |
| end; | |
| else | |
| TFilter.Seek(Pos); | |
| end; | |
| end; | |
| end; | |
| procedure TTextFilter.Truncate; | |
| begin | |
| Flush; | |
| TFilter.Truncate; | |
| end; | |
| procedure TTextFilter.Write(var Buf; Count : Word); | |
| var | |
| inbuf : Tbyte_array absolute buf; | |
| size : word; | |
| begin | |
| with StreamTextRec(TextPtr^) do | |
| begin | |
| if mode <> fmOutput then | |
| begin | |
| if mode = fmInput then | |
| Flush; | |
| TFilter.write(buf,count); | |
| end | |
| else | |
| begin | |
| size := minword(Count, BufSize-BufPos); | |
| move(inbuf,bufptr^[BufPos],size); | |
| dec(count,size); | |
| inc(BufPos,size); | |
| if count > 0 then | |
| begin | |
| Flush; | |
| TFilter.write(inbuf[size],count); | |
| end; | |
| end; | |
| end; | |
| end; | |
| function DoOldCall(Func : TFDDfunc; var F : Text) : Integer; | |
| var | |
| Save : TSaveText; | |
| begin | |
| if @Func <> nil then | |
| with StreamTextRec(F) do | |
| begin | |
| Move(OpenFunc, Save, SizeOf(TSaveText)); | |
| Move(SaveData^, OpenFunc, SizeOf(TSaveText)); { Now using old functions } | |
| DoOldCall := Func(F); | |
| Move(OpenFunc, Save.SaveData^, SizeOf(TSaveText)); { Save any changes } | |
| Move(Save, OpenFunc, SizeOf(TSaveText)); { Back to new ones } | |
| end; | |
| end; | |
| function LogIn(var F : Text) : Integer; Far; | |
| var | |
| Result : Integer; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| Result := DoOldCall(SaveData^.InOutFunc, F); | |
| if Result = 0 then | |
| S^.Write(BufPtr^, BufEnd); { Might want to record errors | |
| here } | |
| LogIn := Result; | |
| end; | |
| end; | |
| function LogOut(var F : Text) : Integer; Far; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| S^.Write(BufPtr^, BufPos); | |
| LogOut := DoOldCall(SaveData^.InOutFunc, F); | |
| end; | |
| end; | |
| function LogInFlush(var F : Text) : Integer; Far; | |
| begin | |
| with StreamTextRec(F) do | |
| LogInFlush := DoOldCall(SaveData^.FlushFunc, F); | |
| end; | |
| function LogOutFlush(var F : Text) : Integer; Far; | |
| var | |
| OldPos : Word; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| OldPos := BufPos; | |
| LogOutFlush := DoOldCall(SaveData^.FlushFunc, F); | |
| if BufPos = 0 then | |
| S^.Write(BufPtr^, OldPos); | |
| end; | |
| end; | |
| function LogClose(var F : Text) : Integer; Far; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| LogClose := DoOldCall(SaveData^.CloseFunc, F); | |
| if not PLogFilter(S)^.Unlog(F) then | |
| { Bug! } ; | |
| end; | |
| end; | |
| function LogOpen(var F : Text) : Integer; Far; | |
| begin | |
| with StreamTextRec(F) do | |
| begin | |
| LogOpen := DoOldCall(SaveData^.OpenFunc, F); | |
| case Mode of | |
| fmInOut, fmOutput : begin | |
| InOutFunc := LogOut; | |
| if @FlushFunc <> nil then | |
| FlushFunc := LogOutFlush; | |
| end; | |
| fmInput : begin | |
| InOutFunc := LogIn; | |
| if @FlushFunc <> nil then | |
| FlushFunc := LogInFlush; | |
| end; | |
| end; | |
| end; | |
| end; | |
| { ******* TLogFilter methods ******** } | |
| constructor TLogFilter.Init(Abase:PStream); | |
| begin | |
| if not TFilter.init(ABase) then | |
| fail; | |
| LogList := nil; | |
| end; | |
| destructor TLogFilter.Done; | |
| begin | |
| while (LogList <> nil) and Unlog(LogList^) do ; | |
| TFilter.Done; | |
| end; | |
| procedure TLogFilter.Log(var F : Text); | |
| var | |
| Save : PSaveText; | |
| OldOpen : TFDDfunc; | |
| Junk : Integer; | |
| begin | |
| New(Save); | |
| with StreamTextRec(F) do | |
| begin | |
| Move(OpenFunc, Save^, SizeOf(TSaveText)); { Save the original contents } | |
| S := @Self; | |
| SaveData := Save; | |
| Next := PStreamTextRec(LogList); | |
| LogList := @F; { Insert this file into the list of logged files } | |
| OldOpen := SaveData^.OpenFunc; | |
| Pointer(@SaveData^.OpenFunc) := nil; { Call LogOpen, but don't open. } | |
| Junk := LogOpen(F); | |
| SaveData^.OpenFunc := OldOpen; | |
| CloseFunc := LogClose; | |
| end; | |
| end; | |
| function TLogFilter.Unlog(var F : Text) : Boolean; | |
| var | |
| Save : PSaveText; | |
| Prev : PStreamTextRec; | |
| begin | |
| Unlog := False; { Assume failure } | |
| with StreamTextRec(F) do | |
| begin | |
| if S = PFilter(@Self) then | |
| begin | |
| { First, delete it from the list. } | |
| if LogList = @F then | |
| LogList := Pointer(Next) | |
| else | |
| begin | |
| Prev := PStreamTextRec(LogList); | |
| while (Prev^.Next <> nil) and (Prev^.Next <> PStreamTextRec(@F)) do | |
| Prev := Prev^.Next; | |
| if Prev^.Next <> PStreamTextRec(@F) then | |
| Exit; { Couldn't find it in the list!? } | |
| Prev^.Next := Next; | |
| end; | |
| Save := SaveData; | |
| Move(Save^, OpenFunc, SizeOf(TSaveText)); | |
| Dispose(Save); | |
| Unlog := True; | |
| end; | |
| end; | |
| end; | |
| {$ifdef overlays} | |
| { ****** Overlay stream code ****** } | |
| type | |
| { This is the structure at the start of each "thunk" segment } | |
| POvrhead = ^TOvrhead; | |
| TOvrhead = record | |
| Signature : Word; { CD 3F - INT 3F call used on returns } | |
| Ret_Ofs : Word; { The offset to jump to when a return triggers a | |
| reload } | |
| Offset : LongInt; { The offset to the segment in the .OVR file } | |
| Code_Bytes, { Size of the code image } | |
| Reloc_Bytes, { Number of relocation fixups times 2 } | |
| Entry_Count, { The number of entry points } | |
| NextSeg, { Next overlay segment - add prefixseg + $10 to find | |
| thunks. List starts with System.ovrcodelist. } | |
| LoadSeg, { The segment at which the overlay is loaded, or 0 } | |
| Reprieve, { Set to 1 to if overlay used while on probation } | |
| NextLoaded : Word; { The segment of the next loaded overlay. List starts | |
| with System.ovrloadlist. Updated *after* call to | |
| ovrreadbuf. } | |
| case Integer of | |
| 1 : (EMSPage, { The EMS page where this overlay is stored } | |
| EMSOffset : Word); { The offset within the EMS page } | |
| 2 : (S : PStream; { The stream holding this segment's code } | |
| Soffset : LongInt); { The offset within S } | |
| end; | |
| var | |
| OldReadFunc : OvrReadFunc; | |
| OvrOldExitProc : Pointer; | |
| OvrStream : PStream; | |
| const | |
| OvrStreamInstalled : Boolean = False; | |
| OvrExitHandler : Boolean = False; | |
| function OvrPtr(Seg : Word) : POvrhead; | |
| { Convert map style segment number, as used by overlay manager, to | |
| pointer } | |
| begin | |
| OvrPtr := Ptr(Seg+PrefixSeg+$10, 0); | |
| end; | |
| function StdPtr(Seg : Word) : POvrhead; | |
| { Convert straight segment number to a pointer } | |
| begin | |
| StdPtr := Ptr(Seg, 0); | |
| end; | |
| function NewReadFunc(OvrSeg : Word) : Integer; Far; | |
| var | |
| Result : Integer; | |
| begin | |
| with StdPtr(OvrSeg)^ do | |
| begin | |
| if S = nil then | |
| begin { Segment not yet loaded } | |
| Result := OldReadFunc(OvrSeg); | |
| if Result = 0 then | |
| begin | |
| { Now copy the loaded code to our stream } | |
| Soffset := OvrStream^.GetSize; | |
| OvrStream^.Seek(Soffset); | |
| OvrStream^.Write(Ptr(LoadSeg, 0)^, Code_Bytes); | |
| Result := OvrStream^.Status; | |
| if Result = stOK then | |
| S := OvrStream | |
| else | |
| OvrStream^.Reset; { Something failed; hope we haven't messed | |
| up the stream too much } | |
| end; | |
| end | |
| else | |
| begin { Segment has been loaded into the stream } | |
| S^.Seek(Soffset); | |
| S^.Read(Ptr(LoadSeg, 0)^, Code_Bytes); | |
| Result := S^.Status; | |
| if Result <> stOK then | |
| begin | |
| S^.Reset; { Fix the stream, and try a standard load } | |
| Result := OldReadFunc(OvrSeg); | |
| end; | |
| end; | |
| end; | |
| NewReadFunc := Result; | |
| end; | |
| procedure OvrExitProc; Far; | |
| { Installed exit procedure; disposes of any streams that are still | |
| handling overlays. } | |
| begin | |
| ExitProc := OvrOldExitProc; | |
| OvrDisposeStreams; | |
| end; | |
| procedure OvrInitStream(S : PStream); | |
| begin | |
| if not OvrStreamInstalled then | |
| begin | |
| OldReadFunc := OvrReadBuf; { Install our reader function } | |
| OvrReadBuf := NewReadFunc; | |
| OvrStreamInstalled := True; | |
| end; | |
| if not OvrExitHandler then | |
| begin | |
| OvrOldExitProc := ExitProc; | |
| ExitProc := @OvrExitProc; | |
| OvrExitHandler := True; | |
| end; | |
| OvrStream := S; { And set stream to use } | |
| end; | |
| procedure OvrDetachStream(BadS : PStream); | |
| var | |
| OvrSeg : Word; | |
| begin | |
| if OvrStreamInstalled then | |
| begin | |
| if OvrStream = BadS then | |
| OvrStream := nil; { Detach default stream } | |
| OvrSeg := OvrCodeList; | |
| while OvrSeg <> 0 do { Walk the overlay list } | |
| with OvrPtr(OvrSeg)^ do | |
| begin | |
| if S <> nil then | |
| begin | |
| if S <> BadS then | |
| begin | |
| if OvrStream = nil then | |
| OvrStream := S; { Set default stream to first found } | |
| end | |
| else | |
| S := nil; { Blank out BadS references } | |
| end; | |
| OvrSeg := NextSeg; | |
| end; | |
| if OvrStream = nil then | |
| begin | |
| OvrStreamInstalled := False; { If we don't have a stream, better | |
| uninstall. } | |
| OvrReadBuf := OldReadFunc; | |
| end; | |
| end; | |
| end; | |
| procedure OvrDisposeStreams; | |
| var | |
| S : PStream; | |
| begin | |
| while OvrStreamInstalled and (OvrStream <> nil) do | |
| begin | |
| S := OvrStream; | |
| OvrDetachStream(S); | |
| Dispose(S, Done); | |
| end; | |
| end; | |
| function OvrSizeNeeded : LongInt; | |
| var | |
| OvrSeg : Word; | |
| Result : LongInt; | |
| begin | |
| OvrSeg := OvrCodeList; | |
| Result := 0; | |
| while OvrSeg <> 0 do { Walk the overlay list } | |
| with OvrPtr(OvrSeg)^ do | |
| begin | |
| if S = nil then | |
| Inc(Result, Code_Bytes); | |
| OvrSeg := NextSeg; | |
| end; | |
| OvrSizeNeeded := Result; | |
| end; | |
| function OvrLoadAll : Boolean; | |
| var | |
| OvrSeg : Word; | |
| Junk : Integer; | |
| begin | |
| if not OvrStreamInstalled then | |
| OvrLoadAll := False | |
| else | |
| begin | |
| OvrClearBuf; | |
| OvrSeg := OvrCodeList; | |
| while OvrSeg <> 0 do { Walk the overlay list } | |
| with OvrPtr(OvrSeg)^ do | |
| begin | |
| if S = nil then | |
| begin | |
| LoadSeg := OvrHeapOrg; { load at start of overlay buffer } | |
| Junk := NewReadFunc(OvrSeg+PrefixSeg+$10); | |
| LoadSeg := 0; { Don't really want it loaded yet } | |
| end; | |
| OvrSeg := NextSeg; | |
| end; | |
| OvrLoadAll := OvrStream^.Status = stOK; | |
| end; | |
| end; | |
| {$endif windows} | |
| { ****** Bit filter code ****** } | |
| constructor TBitFilter.Init(ABase : PStream); | |
| begin | |
| TFilter.Init(ABase); | |
| BitPos := 0; | |
| AtEnd := false; | |
| end; | |
| procedure TBitFilter.PrepareBuffer(ForRead : Boolean); | |
| begin | |
| if BitPos = 8 then { Buffer full on write } | |
| begin | |
| Base^.Write(Buffer, 1); | |
| BitPos := 0; | |
| end; | |
| if BitPos = 0 then { Buffer empty } | |
| begin | |
| if not AtEnd then | |
| begin | |
| if not ForRead then | |
| AtEnd := (Base^.GetPos >= Base^.GetSize); | |
| if (not AtEnd) or ForRead then | |
| begin | |
| Base^.Read(Buffer,1); | |
| BitPos := -8 | |
| end; | |
| end; | |
| if AtEnd then | |
| Buffer := 0; | |
| Mask := 1; | |
| end; | |
| if (not ForRead) and (BitPos < 0) then | |
| begin | |
| Base^.Seek(Base^.GetPos-1); | |
| Inc(BitPos, 8); | |
| AtEnd := false; | |
| end; | |
| end; | |
| function TBitFilter.GetBit : TBit; | |
| begin | |
| if CheckStatus then | |
| begin | |
| PrepareBuffer(True); | |
| GetBit := TBit((Buffer and Mask) > 0); | |
| Mask := Mask shl 1; | |
| Inc(BitPos); | |
| CheckBase; | |
| end; | |
| end; | |
| function TBitFilter.GetBits(Count : Byte) : LongInt; | |
| var | |
| Result : LongInt; | |
| begin | |
| Result := 0; | |
| ReadBits(Result, Count); | |
| GetBits := Result; | |
| end; | |
| procedure TBitFilter.PutBit(ABit : TBit); | |
| begin | |
| if CheckStatus then | |
| begin | |
| PrepareBuffer(False); | |
| if ABit = 1 then | |
| Buffer := Buffer or Mask; | |
| Mask := Mask shl 1; | |
| Inc(BitPos); | |
| end; | |
| end; | |
| procedure TBitFilter.PutBits(ABits : LongInt; Count : Byte); | |
| begin | |
| WriteBits(ABits, Count); | |
| end; | |
| procedure TBitFilter.ReadBits(var Buf; Count : LongInt); | |
| var | |
| w : Word; | |
| b : array[1..2] of Byte absolute w; | |
| bBuf : TByte_Array absolute Buf; | |
| i, Bytes : Word; | |
| Shift : Word; | |
| begin | |
| if (Count > 0) and CheckStatus then | |
| begin | |
| PrepareBuffer(True); | |
| if BitPos > 0 then | |
| begin | |
| Base^.Write(Buffer, 1); | |
| Dec(BitPos, 8); | |
| end; | |
| Shift := BitPos+8; { the number of bits to shift by } | |
| Bytes := (Count+Shift-1) div 8; { Count of whole bytes to read } | |
| if Bytes > 0 then | |
| begin | |
| TFilter.Read(Buf, Bytes); | |
| b[1] := Buffer; | |
| for i := 0 to Pred(Bytes) do | |
| begin | |
| b[2] := bBuf[i]; | |
| w := w shr Shift; | |
| bBuf[i] := b[1]; | |
| w := w shr (8-Shift); | |
| end; | |
| Buffer := b[1]; | |
| end; | |
| { Now fix up the last few bits } | |
| Dec(Count, 8*LongInt(Bytes)); | |
| if Count > 0 then | |
| bBuf[Bytes] := (Buffer shr Shift) and not($FF shl Count) | |
| else | |
| if Count < 0 then | |
| bBuf[Bytes-1] := bBuf[Bytes-1] and not($FF shl (8+Count)); | |
| BitPos := BitPos+Count; | |
| Mask := 1 shl (BitPos+8); | |
| end; | |
| end; | |
| procedure TBitFilter.WriteBits(var Buf; Count : LongInt); | |
| var | |
| w : Word; | |
| b : array[1..2] of Byte absolute w; | |
| bBuf : TByte_Array absolute Buf; | |
| i, Bytes : Word; | |
| Shift : Word; | |
| SaveBuf : Byte; | |
| SavePos : ShortInt; | |
| begin | |
| if CheckStatus then | |
| begin | |
| PrepareBuffer(False); | |
| Bytes := (Count+BitPos-1) div 8; { Count of whole bytes to write } | |
| Shift := 8-BitPos; | |
| if Bytes > 0 then | |
| begin | |
| if Shift < 8 then | |
| begin | |
| b[1] := Buffer shl Shift; | |
| for i := 0 to Pred(Bytes) do | |
| begin | |
| b[2] := bBuf[i]; | |
| w := w shr Shift; | |
| Base^.Write(b[1], 1); | |
| w := w shr (8-Shift); | |
| end; | |
| Buffer := b[1] shr Shift; | |
| end | |
| else | |
| Base^.Write(Buf, Bytes); | |
| end; | |
| Dec(Count, 8*LongInt(Bytes)); | |
| if Count > 0 then | |
| Buffer := (Buffer or (bBuf[Bytes] shl (8-Shift))); | |
| BitPos := BitPos+Count; | |
| if BitPos > 0 then { Fill in upper part of buffer } | |
| begin | |
| SaveBuf := Buffer; | |
| SavePos := BitPos; | |
| BitPos := 0; { signal empty buffer } | |
| PrepareBuffer(False); { and load it } | |
| Buffer := (Buffer and ($FF shl SavePos)) { old part } | |
| or (SaveBuf and not($FF shl SavePos)); { new part } | |
| BitPos := SavePos; | |
| end; | |
| Mask := 1 shl BitPos; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TBitFilter.Flush; | |
| begin | |
| if CheckStatus then | |
| begin | |
| if BitPos > 0 then | |
| Base^.Write(Buffer, 1); | |
| Dec(BitPos, 8); | |
| AtEnd := false; | |
| CheckBase; | |
| end; | |
| end; | |
| procedure TBitFilter.Seek(Pos : LongInt); | |
| begin | |
| if CheckStatus then | |
| begin | |
| Flush; | |
| TFilter.Seek(Pos); | |
| BitPos := 0; | |
| AtEnd := false; | |
| end; | |
| end; | |
| procedure TBitFilter.Read(var Buf; Count : Word); | |
| begin | |
| ReadBits(Buf, 8*LongInt(Count)); | |
| end; | |
| procedure TBitFilter.Write(var Buf; Count : Word); | |
| begin | |
| WriteBits(Buf, 8*LongInt(Count)); | |
| end; | |
| procedure TBitFilter.SeekBit(Pos : LongInt); | |
| var | |
| i : Byte; | |
| b : TBit; | |
| begin | |
| if CheckStatus then | |
| begin | |
| Seek(Pos div 8); | |
| for i := 1 to (Pos and 7) do | |
| b := GetBit; | |
| end; | |
| end; | |
| function TBitFilter.GetBitPos : LongInt; | |
| begin | |
| GetBitPos := 8*TFilter.GetPos+BitPos; { Need TFilter override in | |
| case descendants override | |
| GetPos } | |
| end; | |
| procedure TBitFilter.CopyBits(var S : TBitFilter; Count : LongInt); | |
| var | |
| localbuf : array[1..256] of Byte; | |
| begin | |
| while Count > 2048 do | |
| begin | |
| S.ReadBits(localbuf, 2048); | |
| WriteBits(localbuf, 2048); | |
| Dec(Count, 2048); | |
| end; | |
| if Count > 0 then | |
| begin | |
| S.ReadBits(localbuf, Count); | |
| WriteBits(localbuf, Count); | |
| end; | |
| end; | |
| procedure TBitFilter.ByteAlign; | |
| begin | |
| SeekBit((GetBitPos+7) and $FFFFFFF8); | |
| end; | |
| { ****** Duplicate filter code ****** } | |
| constructor TDupFilter.Init(ABase, ABase2 : PStream); | |
| { Initialize the filter with the given bases. } | |
| begin | |
| if not TFilter.Init(Abase) then | |
| fail; | |
| Base2 := ABase2; | |
| CheckBase2; | |
| if Status = stOK then | |
| Startofs2 := Base2^.GetPos; | |
| end; | |
| destructor TDupFilter.Done; | |
| { Flush filter, then dispose of both bases. } | |
| begin | |
| Flush; | |
| if Base2 <> nil then | |
| Dispose(Base2,done); | |
| TFilter.Done; | |
| end; | |
| function TDupFilter.MisMatch(var buf1,buf2;count:word):word; | |
| var | |
| i : word; | |
| bbuf1 : TByte_Array absolute buf1; | |
| bbuf2 : TByte_Array absolute buf2; | |
| begin | |
| for i := 0 to pred(count) do | |
| if bbuf1[i] <> bbuf2[i] then | |
| begin | |
| MisMatch := succ(i); | |
| exit; | |
| end; | |
| MisMatch := 0; | |
| end; | |
| procedure TDupFilter.Read(var Buf; Count : Word); | |
| var | |
| bpos : word; | |
| localbuf : array[0..255] of byte; | |
| procedure CompareBuffer(size:word); | |
| var | |
| epos : word; | |
| bbuf : TByte_Array absolute Buf; | |
| begin | |
| Base2^.Read(localbuf,size); | |
| dec(count,size); | |
| CheckBase2; | |
| if status = stOK then | |
| begin | |
| epos := MisMatch(bbuf[bpos],localbuf,size); | |
| if epos <> 0 then | |
| Error(stMismatch,bpos+epos); | |
| end; | |
| inc(bpos,size); | |
| end; | |
| begin | |
| TFilter.Read(buf, Count); | |
| bpos := 0; | |
| While (Status = stOK) and (Count >= sizeof(localbuf)) do | |
| CompareBuffer(Sizeof(localbuf)); | |
| If (Status = stOK) and (Count > 0) then | |
| CompareBuffer(Count); | |
| { Be sure the bases are synchronized } | |
| Base2^.Seek(GetPos+StartOfs2); | |
| end; | |
| procedure TDupFilter.Seek(Pos : LongInt); | |
| begin | |
| TFilter.Seek(Pos); | |
| if Status = stOK then | |
| begin | |
| base2^.Seek(pos+startofs2); | |
| CheckBase2; | |
| end; | |
| end; | |
| procedure TDupFilter.Truncate; | |
| begin | |
| TFilter.Truncate; | |
| if Status = stOK then | |
| begin | |
| base2^.truncate; | |
| CheckBase2; | |
| end; | |
| end; | |
| procedure TDupFilter.Write(var Buf; Count : Word); | |
| begin | |
| TFilter.Write(buf,Count); | |
| if Status = stOK then | |
| begin | |
| Base2^.write(buf,Count); | |
| CheckBase2; | |
| end; | |
| end; | |
| procedure TDupFilter.Flush; | |
| begin | |
| TFilter.Flush; | |
| if Status = stOK then | |
| begin | |
| base2^.flush; | |
| CheckBase2; | |
| end; | |
| end; | |
| function TDupFilter.CheckStatus : Boolean; | |
| begin | |
| if TFilter.CheckStatus then | |
| if Base2^.Status <> stOK then | |
| Base2^.Reset; | |
| CheckStatus := Status = stOK; | |
| end; | |
| procedure TDupFilter.CheckBase2; | |
| begin | |
| if Base2^.status <> stOk then | |
| Error(stBase2Error,Base2^.status); | |
| end; | |
| { ****** Concatenating Filter code ****** } | |
| constructor TConcatFilter.Init(ABase, ABase2 : PStream); | |
| { Initialize the filter with the given bases. } | |
| begin | |
| if not TFilter.Init(ABase) then | |
| fail; | |
| Base2 := ABase2; | |
| CheckBase2; | |
| Base1Size := TFilter.GetSize; | |
| if Status = stOK then | |
| StartOfs2 := Base2^.GetPos; | |
| Position := Base1Size; | |
| end; | |
| destructor TConcatFilter.done; | |
| begin | |
| Flush; | |
| if Base2 <> nil then | |
| Dispose(Base2,done); | |
| if Base <> nil then | |
| Dispose(Base,Done); { Can't call TFilter.Done!!!! } | |
| TStream.done; | |
| end; | |
| function TConcatFilter.GetPos:longint; | |
| begin | |
| GetPos := Position; | |
| end; | |
| function TConcatFilter.GetSize:longint; | |
| begin | |
| if CheckStatus then | |
| begin | |
| GetSize := Base1Size + Base2^.GetSize; | |
| CheckBase2; | |
| end; | |
| end; | |
| procedure TConcatFilter.Read(var Buf; Count : Word); | |
| var | |
| Buffer : TByte_array absolute Buf; | |
| base1part : word; | |
| begin | |
| { First read the Base 1 portion } | |
| if Position < Base1Size then | |
| begin | |
| base1part := Count; | |
| if Position+base1part > Base1Size then | |
| base1part := Base1Size - Position; | |
| TFilter.Read(Buf, base1part); | |
| dec(Count,base1part); | |
| inc(Position,Base1part); | |
| if Count > 0 then | |
| Base2^.Seek(StartOfs2); { Be sure Base2 agrees with Pos now } | |
| end | |
| else | |
| base1part := 0; | |
| { Now read the Base 2 portion } | |
| if (Count > 0) and (status = stOK) then | |
| begin | |
| if Position = Base1Size then | |
| Base2^.Seek(StartOfs2); | |
| Base2^.Read(Buffer[base1part],Count); | |
| CheckBase2; | |
| inc(Position,count); | |
| end; | |
| end; | |
| procedure TConcatFilter.Seek(Pos : LongInt); | |
| begin | |
| if Pos < Base1Size then | |
| TFilter.Seek(Pos) | |
| else | |
| begin | |
| if CheckStatus then | |
| begin | |
| Base2^.Seek(Pos-Base1Size+StartOfs2); | |
| CheckBase2; | |
| end; | |
| end; | |
| if Status = stOK then | |
| Position := Pos; | |
| end; | |
| procedure TConcatFilter.Truncate; | |
| begin | |
| if Position < Base1Size then | |
| Error(stUnsupported,0) { We don't allow Base to be truncated, only | |
| Base2 } | |
| else | |
| if CheckStatus then | |
| begin | |
| Base2^.Truncate; | |
| CheckBase2; | |
| end; | |
| end; | |
| procedure TConcatFilter.Write(var Buf; Count : Word); | |
| var | |
| Buffer : TByte_array absolute Buf; | |
| base1part : word; | |
| begin | |
| { First write the Base 1 portion } | |
| if Position < Base1Size then | |
| begin | |
| base1part := Count; | |
| if Position+base1part > Base1Size then | |
| base1part := Base1Size - Position; | |
| TFilter.Write(Buf, base1part); | |
| dec(Count,base1part); | |
| inc(Position,Base1part); | |
| if Count > 0 then | |
| Base2^.Seek(StartOfs2); { Be sure Base2 agrees with Pos now } | |
| end | |
| else | |
| base1part := 0; | |
| { Now write the Base 2 portion } | |
| if (Count > 0) and (status = stOK) then | |
| begin | |
| Base2^.Write(Buffer[base1part],Count); | |
| CheckBase2; | |
| inc(Position,count); | |
| end; | |
| end; | |
| procedure TConcatFilter.Flush; | |
| begin | |
| TFilter.Flush; | |
| if status = stOK then | |
| begin | |
| Base2^.Flush; | |
| CheckBase2; | |
| end; | |
| end; | |
| function TConcatFilter.CheckStatus : Boolean; | |
| begin | |
| if TFilter.CheckStatus then | |
| if Base2^.Status <> stOK then | |
| Base2^.Reset; | |
| CheckStatus := Status = stOK; | |
| end; | |
| procedure TConcatFilter.CheckBase2; | |
| begin | |
| if Base2^.status <> stOk then | |
| Error(stBase2Error,Base2^.status); | |
| end; | |
| { ****** Limit Filter code *****} | |
| constructor TLimitFilter.init(ABase:PStream;ALoLimit,AHiLimit:longint); | |
| { Does the usual init, sets the limits, then does a Seek to ALoLimit | |
| if it is non-zero. } | |
| begin | |
| if not TFilter.Init(ABase) then | |
| fail; | |
| LoLimit := ALoLimit; | |
| HiLimit := AHiLimit; | |
| if ALoLimit <> 0 then | |
| Seek(ALoLimit); | |
| end; | |
| procedure TLimitFilter.Read(var Buf; Count : Word); | |
| begin | |
| if status = stOk then | |
| begin | |
| if GetPos + Count > HiLimit then | |
| begin | |
| Error(stReadError,0); | |
| Fillchar(Buf,Count,0); | |
| end | |
| else | |
| TFilter.Read(Buf,Count); | |
| end; | |
| end; | |
| procedure TLimitFilter.Seek(Pos : LongInt); | |
| begin | |
| if Status = stOK then | |
| begin | |
| if (Pos < LoLimit) or (Pos > HiLimit) then | |
| Error(stReadError,0) | |
| else | |
| TFilter.Seek(Pos); | |
| end; | |
| end; | |
| procedure TLimitFilter.Write(var Buf; Count : Word); | |
| begin | |
| if Status = stOk then | |
| begin | |
| if GetPos + Count > HiLimit then | |
| Error(stWriteError,0) | |
| else | |
| TFilter.Write(Buf,Count); | |
| end; | |
| end; | |
| function TLimitFilter.GetSize:longint; | |
| var | |
| result : longint; | |
| begin | |
| result := TFilter.GetSize; | |
| if result > HiLimit then | |
| GetSize := HiLimit | |
| else | |
| GetSize := result; | |
| end; | |
| { ****** Loop Filter code *****} | |
| procedure TLoopFilter.Read(var Buf; Count : Word); | |
| var | |
| buffer : TByte_Array absolute Buf; | |
| pos : word; | |
| begin | |
| if status = stOk then | |
| begin | |
| if GetPos + Count > HiLimit then | |
| begin | |
| pos := HiLimit - GetPos; | |
| TFilter.Read(Buf,pos); | |
| dec(count,pos); | |
| TFilter.Seek(LoLimit); | |
| Read(Buffer[pos],Count); { Recursive call! } | |
| end | |
| else | |
| Tfilter.Read(Buf,Count); | |
| end; | |
| end; | |
| procedure TLoopFilter.Seek(Pos : LongInt); | |
| var | |
| size : longint; | |
| begin | |
| size := HiLimit - LoLimit; | |
| if Pos < LoLimit then | |
| Pos := LoLimit + (Pos - LoLimit) mod Size + Size; | |
| TFilter.Seek(LoLimit + (Pos - LoLimit) mod Size); | |
| end; | |
| procedure TLoopFilter.Write(var Buf; Count : Word); | |
| var | |
| buffer : TByte_Array absolute Buf; | |
| pos : word; | |
| begin | |
| if status = stOk then | |
| begin | |
| if GetPos + Count > HiLimit then | |
| begin | |
| pos := HiLimit - GetPos; | |
| TFilter.Write(Buf,pos); | |
| dec(count,pos); | |
| TFilter.Seek(LoLimit); | |
| Write(Buffer[pos],Count); { Recursive call! } | |
| end | |
| else | |
| Tfilter.Write(Buf,Count); | |
| end; | |
| end; | |
| function TLoopFilter.GetSize:longint; | |
| var | |
| result : longint; | |
| begin | |
| result := TFilter.GetSize; | |
| if result > HiLimit then | |
| GetSize := HiLimit - LoLimit | |
| else | |
| GetSize := result - LoLimit; | |
| end; | |
| { ****** TReverseFilter code ******} | |
| constructor TReverseFilter.Init(ABase : PStream; AReverseBlocks:boolean); | |
| begin | |
| TFilter.Init(ABase); | |
| ReverseBlocks := AReverseBlocks; | |
| end; | |
| function TReverseFilter.GetPos:longint; | |
| begin | |
| GetPos := TFilter.GetSize-TFilter.GetPos; | |
| end; | |
| procedure TReverseFilter.Read(var Buf;Count : word); | |
| var | |
| curpos : longint; | |
| begin | |
| curpos := TFilter.GetPos; { We call the Tfilter methods to propagate errors } | |
| Base^.Seek(curpos-Count); | |
| Base^.Read(Buf,Count); | |
| if ReverseBlocks then | |
| ReverseBytes(Buf,Count); | |
| TFilter.Seek(curpos-Count); | |
| end; | |
| procedure TReverseFilter.Write(var Buf;Count : word); | |
| var | |
| curpos : longint; | |
| begin | |
| curpos := TFilter.GetPos; { We call the Tfilter methods to propagate errors } | |
| Base^.Seek(curpos-Count); | |
| if ReverseBlocks then | |
| ReverseBytes(Buf,Count); | |
| Base^.Write(Buf,Count); | |
| if ReverseBlocks then | |
| ReverseBytes(Buf,Count); | |
| TFilter.Seek(curpos-Count); | |
| end; | |
| procedure TReverseFilter.Seek(Pos:Longint); | |
| begin | |
| TFilter.Seek(TFilter.GetSize-Pos); | |
| end; | |
| procedure TReverseFilter.Truncate; | |
| begin | |
| Error(stUnsupported,0); | |
| end; | |
| procedure ReverseBytes(var Buf; Count:Word); | |
| var | |
| buffer : TByte_Array absolute Buf; | |
| i,j : word; | |
| t : byte; | |
| begin | |
| if Count > 1 then | |
| begin | |
| j := Count-1; | |
| for i:=0 to (Count div 2) - 1 do | |
| begin | |
| t := buffer[i]; | |
| buffer[i] := buffer[j]; | |
| buffer[j] := t; | |
| dec(j); | |
| end; | |
| end; | |
| end; | |
| { ****** Checksum/CRC code ******} | |
| Function UpdateChksum(initsum:word; var Inbuf; inlen:word):word; | |
| var | |
| i : word; | |
| bbuf : TByte_Array absolute inbuf; | |
| begin | |
| for i:=0 to pred(inlen) do | |
| inc(initsum,bbuf[i]); | |
| UpdateChksum := initsum; | |
| end; | |
| { From the original CRC.PAS: } | |
| { This unit provides three speed-optimized functions to compute (or continue | |
| computation of) a Cyclic Redundency Check (CRC). These routines are | |
| contributed to the public domain (with the limitations noted by the | |
| original authors in the TASM sources). | |
| Each function takes three parameters: | |
| InitCRC - The initial CRC value. This may be the recommended initialization | |
| value if this is the first or only block to be checked, or this may be | |
| a previously computed CRC value if this is a continuation. | |
| InBuf - An untyped parameter specifying the beginning of the memory area | |
| to be checked. | |
| InLen - A word indicating the length of the memory area to be checked. If | |
| InLen is zero, the function returns the value of InitCRC. | |
| The function result is the updated CRC. The input buffer is scanned under | |
| the limitations of the 8086 segmented architecture, so the result will be | |
| in error if InLen > 64k - Offset(InBuf). | |
| These conversions were done on 10-29-89 by: | |
| Edwin T. Floyd [76067,747] | |
| #9 Adams Park Court | |
| Columbus, GA 31909 | |
| (404) 576-3305 (work) | |
| (404) 322-0076 (home) | |
| } | |
| Function UpdateCRC16(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| external; {$L crc16.obj} | |
| { I believe this is the CRC used by the XModem protocol. The transmitting | |
| end should initialize with zero, UpdateCRC16 for the block, Continue the | |
| UpdateCRC16 for two nulls, and append the result (hi order byte first) to | |
| the transmitted block. The receiver should initialize with zero and | |
| UpdateCRC16 for the received block including the two byte CRC. The | |
| result will be zero (why?) if there were no transmission errors. (I have | |
| not tested this function with an actual XModem implementation, though I | |
| did verify the behavior just described. See TESTCRC.PAS.) } | |
| Function UpdateCRCArc(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
| external; {$L crcarc.obj} | |
| { This function computes the CRC used by SEA's ARC utility. Initialize | |
| with zero. } | |
| Function UpdateCRC32(InitCRC : LongInt; Var InBuf; InLen : Word) : LongInt; | |
| external; {$L crc32.obj} | |
| { This function computes the CRC used by PKZIP and Forsberg's ZModem. | |
| Initialize with high-values ($FFFFFFFF), and finish by inverting all bits | |
| (Not). } | |
| { ****** Sequential filter code ****** } | |
| procedure TSequential.Seek(pos:longint); | |
| begin | |
| Error(stUnsupported,0); | |
| end; | |
| { ****** Chksum filter code ******} | |
| constructor TChkSumFilter.init(ABase:PStream; AChksum:word); | |
| begin | |
| if not TSequential.init(ABase) then | |
| fail; | |
| Chksum := AChksum; | |
| end; | |
| procedure TChkSumFilter.Read(var buf; Count:word); | |
| begin | |
| TSequential.Read(buf,count); | |
| if status = stOK then | |
| ChkSum := UpdateChksum(ChkSum,buf,Count); | |
| end; | |
| procedure TChkSumFilter.Write(var buf; Count:word); | |
| begin | |
| TSequential.Write(buf,count); | |
| if status = stOk then | |
| ChkSum := UpdateChksum(ChkSum,buf,Count); | |
| end; | |
| { ***** CRC16 filter code ***** } | |
| constructor TCRC16Filter.init(ABase:PStream; ACRC16:word); | |
| begin | |
| if not TSequential.init(ABase) then | |
| fail; | |
| CRC16 := ACRC16; | |
| end; | |
| procedure TCRC16Filter.Read(var buf; Count:word); | |
| begin | |
| TSequential.Read(buf,count); | |
| if status = stOK then | |
| CRC16 := UpdateCRC16(CRC16,buf,count); | |
| end; | |
| procedure TCRC16Filter.Write(var buf; Count:word); | |
| begin | |
| TSequential.Write(buf,count); | |
| if status = stOk then | |
| CRC16 := UpdateCRC16(CRC16,buf,count); | |
| end; | |
| { ***** CRCARC filter code ***** } | |
| constructor TCRCARCFilter.init(ABase:PStream; ACRCARC:word); | |
| begin | |
| if not TSequential.init(ABase) then | |
| fail; | |
| CRCARC := ACRCARC; | |
| end; | |
| procedure TCRCARCFilter.Read(var buf; Count:word); | |
| begin | |
| TSequential.Read(buf,count); | |
| if status = stOK then | |
| CRCARC := UpdateCRCARC(CRCARC,buf,count); | |
| end; | |
| procedure TCRCARCFilter.Write(var buf; Count:word); | |
| begin | |
| TSequential.Write(buf,count); | |
| if status = stOk then | |
| CRCARC := UpdateCRCARC(CRCARC,buf,count); | |
| end; | |
| { ***** CRC32 filter code ***** } | |
| constructor TCRC32Filter.init(ABase:PStream; ACRC32:longint); | |
| begin | |
| if not TSequential.init(ABase) then | |
| fail; | |
| CRC32 := ACRC32; | |
| end; | |
| procedure TCRC32Filter.Read(var buf; Count:word); | |
| begin | |
| TSequential.Read(buf,count); | |
| if status = stOK then | |
| CRC32 := UpdateCRC32(CRC32,buf,count); | |
| end; | |
| procedure TCRC32Filter.Write(var buf; Count:word); | |
| begin | |
| TSequential.Write(buf,count); | |
| if status = stOk then | |
| CRC32 := UpdateCRC32(CRC32,buf,count); | |
| end; | |
| { ****** Null stream code ****** } | |
| constructor TNulStream.Init; | |
| begin | |
| TStream.Init; | |
| Position := 0; | |
| Value := AValue; | |
| end; | |
| function TNulStream.GetPos; | |
| begin | |
| GetPos := Position; | |
| end; | |
| function TNulStream.GetSize; | |
| begin | |
| GetSize := Position; | |
| end; | |
| procedure TNulStream.Read; | |
| begin | |
| FillChar(Buf, Count, Value); | |
| Inc(Position, Count); | |
| end; | |
| procedure TNulStream.Seek; | |
| begin | |
| Position := Pos; | |
| end; | |
| procedure TNulStream.Write; | |
| begin | |
| Inc(Position, Count); | |
| end; | |
| { ****** RAM stream code ****** } | |
| constructor TRAMStream.Init(Asize : Word); | |
| begin | |
| TStream.Init; | |
| Position := 0; | |
| Size := 0; | |
| Alloc := Asize; | |
| if MaxAvail < Alloc then | |
| Fail; | |
| GetMem(Buffer, Alloc); | |
| if Buffer = nil then { !1.6 } | |
| Fail; | |
| OwnMem := True; | |
| FillChar(Buffer^, Alloc, 0); | |
| end; | |
| constructor TRAMStream.UseBuf(ABuffer : Pointer; Asize : Word); | |
| begin | |
| TRAMStream.Init(0); | |
| Alloc := Asize; | |
| Size := Asize; | |
| Buffer := ABuffer; | |
| OwnMem := False; | |
| end; | |
| destructor TRAMStream.Done; | |
| begin | |
| if OwnMem then | |
| FreeMem(Buffer, Alloc); | |
| TStream.Done; | |
| end; | |
| function TRAMStream.GetPos; | |
| { begin Replaced with assembler for speed. | |
| GetPos := Position; | |
| end; } | |
| assembler; | |
| asm | |
| les di,self | |
| mov ax,es:di[Position]; | |
| xor dx,dx | |
| end; | |
| function TRAMStream.GetSize; | |
| { begin Replaced with assembler for speed. | |
| GetSize := Size; | |
| end; } | |
| assembler; | |
| asm | |
| les di,self | |
| mov ax,es:di[size] | |
| xor dx,dx | |
| end; | |
| function CheckInc(var pos:word;count,limit:word):boolean; assembler; | |
| { Increments pos by count, returns false if limit is exceeded } | |
| asm | |
| les di,pos | |
| mov bx,count | |
| mov al,true | |
| add bx,es:[di] | |
| jc @1 { Carry means error } | |
| mov es:[di],bx | |
| sub bx,limit | |
| jbe @2 | |
| @1: | |
| dec ax { Set AX to false } | |
| @2: | |
| end; | |
| procedure TRAMStream.Read; | |
| begin | |
| Move(Buffer^[Position], Buf, Count); | |
| if not CheckInc(Position,Count,Size) then | |
| begin | |
| Error(stReadError,0); | |
| Dec(Position,Count); | |
| FillChar(Buf,Count,0); | |
| end; | |
| end; | |
| procedure TRAMStream.Seek; | |
| begin | |
| if Pos > Size then | |
| Error(stReaderror, 0) | |
| else | |
| Position := Pos; | |
| end; | |
| procedure TRAMStream.Truncate; | |
| begin | |
| Size := Position; | |
| end; | |
| procedure TRAMStream.Write; | |
| begin | |
| if not CheckInc(Position,Count,Alloc) then | |
| Error(stWriteError, 0) | |
| else | |
| begin | |
| Move(Buf, Buffer^[Position-Count], Count); | |
| if Position > Size then | |
| Size := Position; | |
| end; | |
| end; | |
| { ***** EMS stream code ***** } | |
| destructor TEMSStream2.done; | |
| begin | |
| TEMSStream.done; | |
| EMSCurpage := $FFFF; | |
| end; | |
| { ***** XMS stream code ***** } | |
| {$I xmsstrm.inc} | |
| { ***** EMS size code ***** } | |
| function exist_ems:boolean; | |
| const | |
| ems_found : boolean = false; { Used as initialized var } | |
| var | |
| S : TEMSStream2; | |
| begin | |
| if not ems_found then | |
| begin | |
| S.init(1,1); | |
| ems_found := S.status = stOk; | |
| S.done; | |
| end; | |
| exist_ems := ems_found; | |
| end; | |
| function ems_maxavail: longint; | |
| begin | |
| if not exist_ems then | |
| ems_maxavail:=0 | |
| else | |
| asm | |
| mov ah,$42; | |
| int $67 | |
| mov ax,16384 | |
| mul bx | |
| mov word ptr @result,ax | |
| mov word ptr @result[2],dx | |
| end; | |
| end; | |
| function ems_memavail: longint; | |
| begin | |
| ems_memavail := ems_maxavail; | |
| end; | |
| function GetTempList:String; | |
| { Function to get the list of directories for temp files } | |
| var | |
| {$ifdef windows} | |
| p : PChar; | |
| {$endif} | |
| result : string; | |
| begin | |
| {$ifdef windows} | |
| p := GetEnvVar(@TempEnvVar[1]); | |
| if p <> nil then | |
| result := StrPas(p) | |
| else | |
| result := ''; | |
| {$else} | |
| result := GetEnv(TempEnvVar); | |
| {$endif} | |
| if Length(result) = 0 then | |
| result := '.\'; | |
| GetTempList := result; | |
| end; | |
| function GetTempDir(var TempList:string):string; | |
| { Strip one temp directory off the front of the list, and | |
| return it fully qualified, with a '\' at the end. } | |
| var | |
| Semicolon : byte; | |
| result : string; | |
| curdir : string; | |
| begin | |
| Semicolon := Pos(';',TempList); | |
| if Semicolon > 0 then | |
| begin | |
| result := Copy(TempList,1,Semicolon-1); | |
| TempList := Copy(TempList,Semicolon+1,255); | |
| end | |
| else | |
| begin | |
| result := TempList; | |
| TempList := ''; | |
| end; | |
| if result[Length(result)] <> '\' then | |
| result := result+'\'; | |
| if (length(result) < 2) or (result[2] <> ':') then | |
| GetDir(0,curdir) | |
| else | |
| begin | |
| GetDir(ord(upcase(result[1]))-ord('A')+1,curdir); | |
| result := copy(result,3,255); | |
| end; | |
| if (length(result) > 1) and (result[1] <> '\') then | |
| result := curdir + '\' + result | |
| else | |
| result := copy(curdir,1,2) + result; | |
| GetTempDir := result; | |
| end; | |
| function disk_maxavail: longint; | |
| var | |
| templist,tempname : string; | |
| result : longint; | |
| begin | |
| result := 0; | |
| templist := GetTempList; | |
| repeat | |
| tempname := GetTempDir(templist); | |
| result := MaxLong(result, | |
| DiskFree(ord(upcase(tempname[1]))-ord('A')+1)) | |
| until templist = ''; | |
| disk_maxavail := result; | |
| end; | |
| function disk_memavail: longint; | |
| var | |
| templist,tempname : string; | |
| result,space : longint; | |
| disk : byte; | |
| disks : array[1..32] of boolean; | |
| begin | |
| fillchar(disks,sizeof(disks),false); | |
| result := 0; | |
| templist := GetTempList; | |
| repeat | |
| tempname := GetTempDir(templist); | |
| disk := ord(upcase(tempname[1]))-ord('A')+1; | |
| if not disks[disk] then | |
| begin | |
| disks[disk] := true; | |
| space := DiskFree(disk); | |
| end | |
| else | |
| space := 0; | |
| if space > 0 then | |
| inc(result,space); | |
| until templist = ''; | |
| disk_memavail := result; | |
| end; | |
| { ***** Named Buffered file stream code ***** } | |
| constructor TNamedBufStream.Init(Name : FNameStr; Mode : TOpenMode; ABufSize : Word); | |
| begin | |
| if TBufStream.Init(Name, Mode, ABufSize) then | |
| {$ifdef windows} | |
| filename := StrNew(name) | |
| {$else} | |
| Filename := NewStr(Name) | |
| {$endif} | |
| else | |
| Fail; | |
| end; | |
| destructor TNamedBufStream.Done; | |
| begin | |
| {$ifdef windows} | |
| StrDispose(filename); | |
| {$else} | |
| DisposeStr(Filename); | |
| {$endif} | |
| TBufStream.Done; | |
| end; | |
| constructor TTempBufStream.Init(ABufSize : Word; | |
| InitSize,MaxSize : Longint); | |
| var | |
| TempList,TempName : String; | |
| Okay : Boolean; | |
| NewHandle : Word; | |
| F : File; | |
| begin | |
| if not TStream.Init then | |
| Fail; | |
| if MaxAvail < ABufSize then | |
| Fail; | |
| BufSize := ABufSize; | |
| GetMem(Buffer, BufSize); | |
| if Buffer = Nil then { !1.6 } | |
| Fail; | |
| MaxSize := MaxLong(MinLong(MaxSize,Disk_MaxAvail),InitSize); | |
| TempList := GetTempList; | |
| repeat | |
| TempName := GetTempDir(TempList); | |
| FillChar(TempName[Length(TempName)+1], 255-Length(TempName), #0); | |
| asm | |
| push ds | |
| push ss | |
| pop ds | |
| lea dx,TempName[1] | |
| mov ah, $5a | |
| xor cx,cx | |
| {$ifdef windows} | |
| call dos3call | |
| {$else} | |
| int $21 { Create temporary file. } | |
| {$endif} | |
| pop ds | |
| jc @failed | |
| mov Okay,True | |
| mov NewHandle,ax | |
| jmp @done | |
| @failed: | |
| mov Okay,False | |
| @done: | |
| end; | |
| if okay then | |
| begin | |
| Handle := NewHandle; | |
| while TempName[Length(TempName)+1] <> #0 do | |
| Inc(TempName[0]); | |
| {$ifdef windows} | |
| Filename := StrNew(StrPCopy(@tempname[1],tempname)); | |
| {$else} | |
| Filename := NewStr(TempName); | |
| {$endif} | |
| Seek(MaxSize-1); | |
| Write(okay,1); { Write a 0 } | |
| Flush; | |
| Seek(InitSize); | |
| Truncate; | |
| Seek(0); { !1.6} | |
| okay := Status = stOK; | |
| if not okay and (TempList <> '') then | |
| begin | |
| asm | |
| mov ah,$3E | |
| mov bx,NewHandle | |
| int $21 { Close file } | |
| end; | |
| assign(F,filename^); | |
| Erase(F); | |
| Reset; | |
| {$ifdef windows} | |
| StrDispose(Filename); | |
| {$else} | |
| DisposeStr(Filename); | |
| {$endif} | |
| Filename := nil; | |
| end; | |
| end; | |
| until okay or (TempList = ''); | |
| end; | |
| destructor TTempBufStream.Done; | |
| var | |
| F : file; | |
| begin | |
| {$ifdef windows} | |
| assign(f,StrPas(Filename)); | |
| {$else} | |
| Assign(F, Filename^); | |
| {$endif} | |
| TNamedBufStream.Done; | |
| Erase(F); | |
| end; | |
| {******** TWorkStream code ******* } | |
| constructor TWorkStream.init(Allocator:TAllocator;ABlockmin,ABlockMax:Longint; | |
| APreference : TStreamRanking); | |
| begin | |
| TFilter.init(Allocator(ABlockmin,ABlockmax,APreference)); | |
| Allocate := Allocator; | |
| Blockmin := ABlockmin; | |
| Blockmax := ABlockmax; | |
| Preference := APreference; | |
| BlockStart := 0; | |
| end; | |
| procedure TWorkStream.write(var Buf; Count:Word); | |
| var | |
| Buffer : TByte_array absolute Buf; | |
| firstpart : word; | |
| byteswritten : word; | |
| pos : longint; | |
| NewBase : PStream; | |
| saveStatus, saveInfo : integer; | |
| begin | |
| pos := GetPos; | |
| byteswritten := 0; | |
| if CheckStatus then | |
| repeat | |
| firstpart := Count; | |
| if (Pos < BlockStart+BlockMax) and (Pos+firstpart > BlockStart+BlockMax) then | |
| firstpart := BlockStart+BlockMax-Pos; | |
| TFilter.Write(Buffer[byteswritten], firstpart); | |
| { **** crummy code to get around problems with TBufStream **** } | |
| { The test is an efficiency hack - we don't want to flush every | |
| segment of the stream, just the last one. } | |
| if typeof(Base^) = typeof(TConcatFilter) then | |
| PConcatFilter(Base)^.Base2^.Flush | |
| else | |
| Base^.Flush; { Must flush all writes to see TBufStream | |
| errors immediately :-( } | |
| CheckBase; { 1.6 fix } | |
| { **** end of crummy code :-) ***** } | |
| if Status = stOK then | |
| begin | |
| dec(Count,firstpart); | |
| inc(Pos,firstpart); | |
| inc(byteswritten,firstpart); | |
| end | |
| else | |
| begin | |
| saveStatus := Status; | |
| saveInfo := ErrorInfo; | |
| Reset; | |
| if Pos = GetSize then | |
| begin | |
| { If write failed at eof, allocate a new block } | |
| Seek(0); | |
| NewBase := Allocate(BlockMin,BlockMax,Preference); | |
| if (NewBase = nil) or (NewBase^.Status <> stOK) then | |
| begin | |
| error(stBaseError, stWriteError); | |
| exit; | |
| end; | |
| Base := New(PConcatFilter,init(Base,NewBase)); | |
| BlockStart := Pos; | |
| end | |
| else { Some other kind of write failure; restore the error status } | |
| begin | |
| error(saveStatus,saveInfo); | |
| exit; | |
| end; | |
| end; | |
| until count = 0; | |
| end; | |
| { ***** Temp Stream Code ******* } | |
| function TempStream(InitSize, MaxSize : LongInt; | |
| Preference : TStreamRanking) : PStream; | |
| var | |
| Choice : Integer; | |
| Result : PStream; | |
| StreamType : TStreamType; | |
| Nulls : TNulStream; | |
| begin | |
| Result := nil; | |
| Nulls.Init(0); | |
| for Choice := 1 to NumTypes do | |
| begin | |
| StreamType := Preference[Choice]; | |
| case StreamType of | |
| RAMStream : | |
| if MaxSize < $10000 then | |
| Result := New(PRAMStream, Init(MaxSize)); | |
| EMSStream : | |
| if ems_MaxAvail >= MaxSize then | |
| Result := New(PEMSStream2, Init(InitSize, MaxSize)); | |
| XMSStream : | |
| if xms_MaxAvail >= MaxSize then | |
| Result := New(PXMSStream, Init(InitSize, MaxSize)); | |
| FileStream : | |
| if disk_MaxAvail >= MaxSize then | |
| Result := New(PTempBufStream, Init(2048, InitSize, MaxSize)); | |
| end; | |
| if (Result <> nil) and (Result^.Status = stOK) then | |
| begin | |
| FastCopy(Nulls, Result^, InitSize); | |
| Result^.Seek(0); | |
| if Result^.Status = stOK then | |
| begin | |
| Nulls.Done; | |
| TempStream := Result; | |
| Exit; | |
| end; | |
| end; | |
| if Result <> nil then | |
| Dispose(Result, Done); { Clean up and start over } ; | |
| Result := nil; | |
| end; | |
| TempStream := nil; | |
| end; | |
| function StreamName(S:PStream):String; | |
| { This function is for debugging only! It links every single stream | |
| type into your .EXE. } | |
| var | |
| t : pointer; | |
| begin | |
| if S=nil then | |
| StreamName := 'nil' | |
| else | |
| begin | |
| t := typeof(S^); | |
| if t = typeof(TStream) then StreamName := 'TStream' | |
| else if t = typeof(TEMSStream) then StreamName := 'TEMSStream' | |
| else if t = typeof(TDOSStream) then StreamName := 'TDOSStream' | |
| else if t = typeof(TBufStream) then StreamName := 'TBufStream' | |
| else if t = typeof(TFilter) then StreamName := 'TFilter' | |
| else if t = typeof(TEncryptFilter) then StreamName := 'TEncryptFilter' | |
| else if t = typeof(TLZWFilter) then StreamName := 'TLZWFilter' | |
| else if t = typeof(TTextFilter) then StreamName := 'TTextFilter' | |
| else if t = typeof(TLogFilter) then StreamName := 'TLogFilter' | |
| else if t = typeof(TBitFilter) then StreamName := 'TBitFilter' | |
| else if t = typeof(TDupFilter) then StreamName := 'TDupFilter' | |
| else if t = typeof(TConcatFilter) then StreamName := 'TConcatFilter' | |
| else if t = typeof(TLimitFilter) then StreamName := 'TLimitFilter' | |
| else if t = typeof(TLoopFilter) then StreamName := 'TLoopFilter' | |
| else if t = typeof(TReverseFilter) then StreamName := 'TReverseFilter' | |
| else if t = typeof(TSequential) then StreamName := 'TSequential' | |
| else if t = typeof(TChksumFilter) then StreamName := 'TChksumFilter' | |
| else if t = typeof(TCRC16Filter) then StreamName := 'TCRC16Filter' | |
| else if t = typeof(TCRCARCFilter) then StreamName := 'TCRCARCFilter' | |
| else if t = typeof(TCRC32Filter) then StreamName := 'TCRC32Filter' | |
| else if t = typeof(TNulStream) then StreamName := 'TNulStream' | |
| else if t = typeof(TRAMStream) then StreamName := 'TRAMStream' | |
| else if t = typeof(TEMSStream2) then StreamName := 'TEMSStream2' | |
| else if t = typeof(TXMSStream) then StreamName := 'TXMSStream' | |
| else if t = typeof(TNamedBufStream) then StreamName := 'TNamedBufStream' | |
| else if t = typeof(TTempBufStream) then StreamName := 'TTempBufStream' | |
| else if t = typeof(TWorkStream) then StreamName := 'TWorkStream' | |
| else StreamName := 'Unknown (or uninitialized) stream'; | |
| end; | |
| end; | |
| { ******* Fast copy code ******** } | |
| procedure FastCopy(var src,dest:TStream; size:longint); | |
| var | |
| buffer : pbyte_array; | |
| bufsize : word; | |
| begin | |
| bufsize := minlong(minlong(65536-512, maxavail), size); | |
| if bufsize < 512 then | |
| dest.copyfrom(src,size) | |
| else | |
| begin | |
| getmem(buffer,bufsize); | |
| if buffer = nil then | |
| dest.copyfrom(src,size) | |
| else | |
| begin | |
| while size >= bufsize do | |
| begin | |
| src.read(buffer^,bufsize); | |
| dest.write(buffer^,bufsize); | |
| dec(size,bufsize); | |
| end; | |
| if size > 0 then | |
| begin | |
| src.read(buffer^,size); | |
| dest.write(buffer^,size); | |
| end; | |
| freemem(buffer,bufsize); | |
| end; | |
| end; | |
| end; | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {$B-} { Use fast boolean evaluation. } | |
| program textdemo; | |
| { Demonstrates use of TTextFilter and TWorkStream } | |
| {$i stdefine.inc} | |
| uses | |
| {$ifdef windows} wincrt, {$endif} | |
| {$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
| streams; | |
| var | |
| i : integer; | |
| S : PWorkStream; | |
| T : PTextFilter; | |
| offsets : array[1..15000] of longint; | |
| line : string; | |
| startmem : longint; | |
| function MyTempStream(MinSize,MaxSize:Longint; | |
| Preference:TStreamRanking):PStream; far; | |
| var | |
| result : PStream; | |
| begin | |
| Write('S is ',S^.GetSize:6,' bytes, Memavail=',memavail:6); | |
| result := TempStream(MinSize,MaxSize,Preference); | |
| Writeln(', allocated ',StreamName(result)); | |
| MyTempStream := result; | |
| end; | |
| begin | |
| TempEnvVar := 'TMP'; | |
| startmem := Memavail; | |
| { Create a temporary file with blocks in EMS, XMS or on the heap: } | |
| S := New(PWorkStream, Init(TempStream,2048,65520,ForSpeed)); | |
| S^.Allocate := MyTempStream; | |
| if (S=nil) or (S^.status <> stOK) then | |
| begin | |
| writeln('Failed to create temporary stream.'); | |
| halt(99); | |
| end; | |
| { Insert it into a text filter } | |
| New(T, init(S, 'TempStream')); | |
| if (T=nil) or (T^.status <> stOK) then | |
| begin | |
| writeln('Failed to create text filter.'); | |
| halt(98); | |
| end; | |
| { Now write 15000 lines to it. } | |
| Rewrite(T^.TextFile); | |
| for i:=1 to 15000 do | |
| begin | |
| offsets[i] := T^.GetPos; | |
| writeln(T^.TextFile, '------------- Line number ',i:5,' --------------'); | |
| end; | |
| writeln('Have written 15000 lines (',T^.Getsize,' bytes) to work stream.'); | |
| { Now read them back in a strange order. } | |
| Reset(T^.TextFile); | |
| writeln('The first line is '); | |
| Readln(T^.TextFile,line); | |
| writeln(line); | |
| for i:=5 downto 1 do | |
| begin | |
| writeln('Line ',i*100,' is:'); | |
| T^.Seek(offsets[i*100]); | |
| readln(T^.TextFile,line); | |
| writeln(line); | |
| end; | |
| writeln('Lines 14995 onwards are:'); | |
| T^.seek(offsets[14995]); | |
| while not eof(T^.Textfile) do | |
| begin | |
| readln(T^.TextFile,line); | |
| writeln(line); | |
| end; | |
| { All done; dispose of the filter to release the memory } | |
| dispose(T,done); | |
| if memavail <> startmem then | |
| writeln('Error! ',startmem-memavail,' bytes of memory left allocated.'); | |
| writeln('All done!'); | |
| end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { This include file is a slightly modified version of XMSSTRM.PAS, by Stefan | |
| Boether, included here with his kind permission. -djm } | |
| (*****************************************************************************) | |
| (* *) | |
| (* Filename : XMSSTRM.INC *) | |
| (* Autor : Stefan Boether / Compuserve Id : 100023,275 *) | |
| (* System : TURBO 6.00 / MS-DOS 3.2 / Netzwerk *) | |
| (* Aenderung : *) | |
| (* wann was wer *) | |
| (*---------------------------------------------------------------------------*) | |
| (* 22.03.92 Error fixed with NewBlock and UsedBlocks Stefc *) | |
| (* 28.04.92 Size field added, BlockSize made constant DJM *) | |
| (* 15.10.92 Off-by-one bug fixed in Seek method DJM *) | |
| (*****************************************************************************) | |
| (* Beschreibung: Object for an Stream in XMS-Memory *) | |
| (*****************************************************************************) | |
| {Header-End} | |
| {!!!!!!!!!!!!!!! | |
| program Test; | |
| uses objects, XmsStrm; | |
| var T : TXmsStream; | |
| P : PString; | |
| begin | |
| writeln( xms_MaxAvail, ' ', xms_MemAvail ); | |
| T.Init( 20, 20 ); | |
| T.WriteStr( NewStr( 'Hello' )); | |
| T.WriteStr( NewStr( 'World' )); | |
| T.Seek( 0 ); | |
| P := T.ReadStr; | |
| writeln( P^ ); | |
| P := T.ReadStr; | |
| writeln( P^ ); | |
| T.Done; | |
| end. | |
| !!!!!!!!!!!!!!!!} | |
| var xms_IOsts : Byte; | |
| xms_Addr : Pointer; | |
| const | |
| xms_Initialized : Boolean = False; | |
| { This allows us to avoid a unit initialization section } | |
| xms_BlockSize = 1024; | |
| { - Some Xms - Procedures that I need ! -} | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure MoveMem(ToAddress : Pointer; ToHandle : Word; | |
| FromAddress : Pointer; FromHandle : Word; | |
| Size : LongInt); | |
| begin | |
| asm | |
| mov byte ptr xms_IOsts,0 | |
| mov ah,$0B | |
| lea si,Size | |
| push ds | |
| pop es | |
| push ss | |
| pop ds | |
| call es:[xms_Addr] | |
| push es | |
| pop ds | |
| or ax,ax | |
| jnz @@1 | |
| mov byte ptr xms_IOsts,bl | |
| @@1: | |
| end; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| function GetByte(Handle : Word; FromAddress : LongInt) : Byte; | |
| var TempBuf : array[0..1] of Byte; | |
| begin | |
| MoveMem(@TempBuf, 0, Pointer(FromAddress and $FFFFFFFE), Handle, 2); | |
| GetByte := TempBuf[FromAddress and $00000001]; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure SetByte(Handle : Word; ToAddress : LongInt; Value : Byte); | |
| var TempBuf : array[0..1] of Byte; | |
| begin | |
| MoveMem(@TempBuf, 0, Pointer(ToAddress and $FFFFFFFE), Handle, 2); | |
| TempBuf[ToAddress and $00000001] := Value; | |
| MoveMem(Pointer(ToAddress and $FFFFFFFE), Handle, @TempBuf, 0, 2); | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure xms_Init; | |
| begin | |
| if not xms_Initialized then | |
| begin | |
| xms_IOsts := 0; | |
| xms_Addr := nil; | |
| asm | |
| mov ax,$4300 | |
| int $2F | |
| cmp al,$80 | |
| jne @@1 | |
| mov ax,$4310 | |
| int $2F | |
| mov word ptr xms_Addr,bx | |
| mov word ptr xms_Addr+2,es | |
| jmp @@2 | |
| @@1: | |
| mov byte ptr xms_IOsts,$80 | |
| @@2: | |
| end; | |
| if xms_IOsts = 0 then | |
| xms_Initialized := True; | |
| end; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| function xms_GetMem(KB : Word) : Word; Assembler; | |
| asm | |
| mov xms_IOsts,0 | |
| mov ah,$09 | |
| mov dx,word ptr KB | |
| call [xms_Addr] | |
| or ax,ax | |
| jz @@1 | |
| mov ax,dx | |
| jmp @@2 | |
| @@1: | |
| mov byte ptr xms_IOsts,bl | |
| @@2: | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure xms_FreeMem(Handle : Word); | |
| begin | |
| asm | |
| mov xms_IOsts,0 | |
| mov ah,$0A | |
| mov dx,word ptr Handle | |
| call [xms_Addr] | |
| or ax,ax | |
| jnz @@1 | |
| mov byte ptr xms_IOsts,bl | |
| @@1: | |
| end; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure xms_ResizeMem(Size, Handle : Word); | |
| begin | |
| asm | |
| mov ah,$0F | |
| mov bx,word ptr Size | |
| mov dx,word ptr Handle | |
| call [xms_Addr] | |
| or ax,ax | |
| jnz @@1 | |
| mov byte ptr xms_IOsts,bl | |
| @@1: | |
| end; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure xms_MoveFrom(Size, Handle : Word; FromAddress : LongInt; | |
| ToAddress : Pointer); | |
| type ByteArr = array[0..MaxInt] of Byte; | |
| BytePtr = ^ByteArr; | |
| begin | |
| if Size = 0 then Exit; | |
| if Odd(FromAddress) then begin | |
| BytePtr(ToAddress)^[0] := GetByte(Handle, FromAddress); | |
| if xms_IOsts <> 0 then Exit; | |
| Dec(Size); | |
| Inc(FromAddress); | |
| Inc(LongInt(ToAddress)); | |
| end; | |
| MoveMem(ToAddress, 0, Pointer(FromAddress), Handle, Size and $FFFE); | |
| if xms_IOsts <> 0 then Exit; | |
| if Odd(Size) | |
| then BytePtr(ToAddress)^[Size-1] := GetByte(Handle, FromAddress+Size-1); | |
| if xms_IOsts <> 0 then Exit; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| procedure xms_MoveTo(Size, Handle : Word; FromAddress : Pointer; | |
| ToAddress : LongInt); | |
| type ByteArr = array[0..MaxInt] of Byte; | |
| BytePtr = ^ByteArr; | |
| begin | |
| if Size = 0 then Exit; | |
| if Odd(ToAddress) then begin | |
| SetByte(Handle, ToAddress, BytePtr(FromAddress)^[0]); | |
| if xms_IOsts <> 0 then Exit; | |
| Dec(Size); | |
| Inc(LongInt(FromAddress)); | |
| Inc(ToAddress); | |
| end; | |
| MoveMem(Pointer(ToAddress), Handle, FromAddress, 0, Size and $FFFE); | |
| if xms_IOsts <> 0 then Exit; | |
| if Odd(Size) | |
| then SetByte(Handle, ToAddress+Size-1, BytePtr(FromAddress)^[Size-1]); | |
| if xms_IOsts <> 0 then Exit; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| constructor TXMSStream.Init(MinSize, MaxSize : longint); | |
| var | |
| MinBlocks,MaxBlocks : word; | |
| begin | |
| TStream.Init; | |
| xms_Init; | |
| BlocksUsed := 0; | |
| Size := 0; | |
| Position := 0; | |
| Handle := 0; | |
| MaxSize := MinLong(MaxSize,xms_Maxavail); | |
| MaxBlocks := (MaxSize + xms_Blocksize -1) div xms_Blocksize; | |
| MinBlocks := (MinSize + xms_Blocksize -1) div xms_Blocksize; | |
| if MinBlocks < 1 then | |
| MinBlocks := 1; | |
| if MaxBlocks < MinBlocks then | |
| MaxBlocks := MinBlocks; | |
| if xms_IOsts <> $00 then | |
| Error(stInitError, xms_IOsts) | |
| else | |
| begin | |
| Handle := xms_GetMem(MaxBlocks); | |
| if xms_IOsts <> $00 then | |
| Error(stInitError, xms_IOsts) | |
| else | |
| begin | |
| xms_ResizeMem(MinBlocks,Handle); | |
| BlocksUsed := MinBlocks; | |
| if xms_IOsts <> $00 then | |
| Error(stInitError, xms_IOsts); | |
| end; | |
| end; | |
| end; | |
| function TXMSStream.GetPos : LongInt; | |
| begin | |
| GetPos := Position; | |
| end; | |
| function TXMSStream.GetSize : LongInt; | |
| begin | |
| GetSize := Size; | |
| end; | |
| procedure TXMSStream.Read(var Buf; Count : Word); | |
| begin | |
| if Status = stOK then | |
| if Position+Count > Size then | |
| Error(stReaderror, 0) | |
| else | |
| begin | |
| xms_MoveFrom(Count, Handle, Position, @Buf); | |
| if xms_IOsts <> 0 then | |
| Error(stReaderror, xms_IOsts) | |
| else | |
| Inc(Position, Count); | |
| end; | |
| end; | |
| procedure TXMSStream.Seek(Pos : LongInt); | |
| begin | |
| if Status = stOK then | |
| if Pos > Size then { 1.4: bug fix } | |
| Error(stReaderror, Pos) | |
| else | |
| Position := Pos; | |
| end; | |
| procedure TXMSStream.Truncate; | |
| begin | |
| if Status = stOK then | |
| begin | |
| Size := Position; | |
| while (BlocksUsed > (Size div xms_BlockSize+1)) do FreeBlock; | |
| end; | |
| end; | |
| procedure TXMSStream.Write(var Buf; Count : Word); | |
| begin | |
| while (Status = stOK) | |
| and (Position+Count > LongMul(xms_BlockSize, BlocksUsed)) do | |
| NewBlock; | |
| if Status = stOK then | |
| begin | |
| xms_MoveTo(Count, Handle, @Buf, Position); | |
| if xms_IOsts <> 0 then | |
| Error(stWriteError, xms_IOsts) | |
| else | |
| Inc(Position, Count); | |
| if Position > Size then | |
| Size := Position; | |
| end; | |
| end; | |
| procedure TXMSStream.NewBlock; | |
| begin | |
| xms_ResizeMem(Succ(BlocksUsed), Handle); | |
| if xms_IOsts <> 0 then | |
| Error(stWriteError, xms_IOsts) | |
| else | |
| Inc(BlocksUsed); | |
| end; | |
| procedure TXMSStream.FreeBlock; | |
| begin | |
| Dec(BlocksUsed); | |
| xms_ResizeMem(BlocksUsed, Handle); | |
| end; | |
| function xms_MaxAvail : Longint; | |
| begin | |
| xms_Init; | |
| if xms_IOsts = 0 then | |
| asm | |
| xor bx, bx { for better error checking, since qemm | |
| 6.0 leaves bl unchanged on success } | |
| mov ah,$08 | |
| call [xms_Addr] | |
| or bl, bl { extended error checking by MM 22.02.93 } | |
| jz @OK | |
| mov byte ptr xms_IOsts,bl | |
| xor ax,ax | |
| @OK: | |
| mov dx,xms_Blocksize | |
| mul dx | |
| mov word ptr @result,ax | |
| mov word ptr @result[2],dx | |
| end | |
| else | |
| xms_MaxAvail := 0; | |
| end; | |
| (* /////////////////////////////////////////////////////////////////////// *) | |
| function xms_MemAvail : Longint; | |
| begin | |
| xms_Init; | |
| if xms_IOsts = 0 then | |
| asm | |
| xor bx, bx { for better error checking, since qemm | |
| 6.0 leaves bl unchanged on success } | |
| mov ah,$08 | |
| call [xms_Addr] | |
| or bl, bl { extended error checking by MM 22.02.93 } | |
| jz @OK | |
| mov byte ptr xms_IOsts,bl | |
| xor dx,dx | |
| @OK: | |
| mov ax,dx | |
| mov dx,xms_blocksize | |
| mul dx | |
| mov word ptr @result,ax | |
| mov word ptr @result[2],dx | |
| end | |
| else | |
| xms_MemAvail := 0; | |
| end; | |
| destructor TXMSStream.Done; | |
| begin | |
| { Seek(0); | |
| Truncate; } | |
| if xms_Initialized and (BlocksUsed > 0) then | |
| xms_FreeMem(Handle); | |
| end; |






KMorwath said
One of the reasons, IMHO, is that while TurboPascal streams were designed as a general I/O mechanism, when Delphi was designed they were tied too much to the form serialization task, losing the big picture. It was one of the (bad) surprise I had when I moved from TP7 to D1.
jpluimers said
I think you’re right. I’m still surprised.