{ SSTP Bottle Surface Preview Plug-In for "SVG" (C)2003 naru, Mikage Sawatari / SSTP Bottle } library SVG; {$R *.res} uses Windows, Classes, SysUtils, Graphics, IniFiles, Dialogs, PngImage, jpeg; const SurfaceWidth = 48; SurfaceHeight = 64; ConfigFile = 'SVG.ini'; var MyPath: String; GhostFile: String; Ghost2File: TStringList; Pic: TPicture; // 読み取った画像(*.png, *.bmp, *.jpg) PicFileName: String; // そのファイル名 // DLLロード時、およびDLL再初期化時に呼ばれる。 // 読み込まれた画像は、Bottle Client側で「サーフィス再読み込み」を // 明示的に指定されない限りは Bottle Client 側でキャッシュされるので、 // DLLで画像のキャッシュなどを考える必要は基本的にない。 // とはいえサーフィス定義ファイルなどを先に解析しておく必要があるなら、 // ここで読み込んでおくと良い。PathにDLLのパスが渡される。 procedure Load(Path: PChar); cdecl; var Ini: TIniFile; begin MyPath := Path; Ini := TIniFile.Create(MyPath + ConfigFile); try GhostFile := Ini.ReadString('SVG', 'GhostFile', ''); finally Ini.Free; end; if not FileExists(GhostFile) then ShowMessage('SVG.dll Warning: Ghost file is not specified'); Ghost2File := TStringList.Create; Pic := TPicture.Create; PicFileName := ''; end; // DLLアンロード時に呼ばれる。 procedure Unload; cdecl; begin Ghost2File.Free; Pic.Free; end; // DLLの名前およびバージョンを返す。 // DLLNameにはDLLの名称や作者、DLL本体のバージョンなどをを簡潔に指定する。 // NameLenバイトを超えてはならない。 // versionにはとりあえず1を返すこと。versionはSSTP Bottle Clientとの // 通信のインターフェースのバージョンであってDLLのバージョンとかではないことに // 注意(Bottle ClientはDLLのバージョン番号なぞ興味ないので)。 // CanConfigureは、Configureを呼ばれてやることがあるかどうかを返す。 function GetVersion(DLLName: PChar; NameLen: integer; var Version: integer; var CanConfigure: boolean): integer; cdecl; const ThisDLL = 'SVG Surface Loader Ver. 2.2'; begin Version := 1; CanConfigure := true; StrLCopy(DLLName, ThisDLL, NameLen); Result := Length(ThisDLL) + 1; end; function LoadDefinitionFileVer2(Ghost: String; Surface: integer; Lines: TStringList; const FileName: String; out Pos: integer): String; var ALine, SurfaceMap: TStringList; i, j, p, sur, oldpos: integer; posstr: String; begin // 内部関数。ゴースト定義ファイルを解析して、 // 目的のゴーストの含まれたファイルかどうか判断する ALine := TStringList.Create; try ALine.CommaText := Lines[0]; if (ALine[0] <> 'GHOST') then Exit; Ghost2File.Values[Ghost] := FileName; // 次からのショートカット if (ALine[1] <> Ghost) then begin Exit; end; // 目的のゴースト発見 oldpos := 0; SurfaceMap := TStringList.Create; try for i := 1 to Lines.Count-1 do begin SurfaceMap.CommaText := Lines[i]; for j := 0 to SurfaceMap.Count-1 do begin try p := System.Pos(':', SurfaceMap[j]); if p = 0 then Continue; sur := StrToInt(Copy(SurfaceMap[j], 1, p-1)); posstr := Copy(SurfaceMap[j], p+1, High(integer)); if posstr = '+' then Pos := oldpos + 1 else Pos := StrToInt(posstr); if sur = Surface then begin Result := ALine[4]; // BMPファイル名 Exit; end else oldpos := Pos; except on EConvertError do; // nothing. 単なるコメント行扱い end; end; end; finally SurfaceMap.Free; end; // BASICの場合の処理 if AnsiCompareText('BASIC', ALine[3]) = 0 then begin if (Surface >= 0) and (Surface <= 11) then begin Result := ALine[4]; // BMPファイル名 Pos := Surface; // BASICの場合は位置とサーフィス番号が一対一対応している Exit; end; end; finally ALine.Free; end; end; procedure ParseKeyVal(const Line: String; out Key, Val: String); var p: integer; begin p := Pos('=', Line); if p > 0 then begin Key := Copy(Line, 1, p-1); Val := AnsiDequotedStr(Copy(Line, p+1, High(integer)), '"'); end else begin Key := ''; Val := Line; end; end; function LoadDefinitionFileVer3(Ghost: String; Surface: integer; Lines: TStringList; const FileName: String; out Pos: integer): String; var i, j, k, smin, smax, oldsur: integer; Key, Val, SurStr, PosStr, SakuraName: String; Dat, Sur2Pos: TStringList; begin oldsur := -1; Sur2Pos := TStringList.Create; try for i := 1 to Lines.Count-1 do // 1行目は"SVG"の文字列 begin ParseKeyVal(Lines[i], Key, Val); if SameText(Key, 'sakura') then begin SakuraName := Val; Ghost2File.Values[SakuraName] := FileName; //次からのショートカット if SakuraName <> Ghost then //別ゴーストの定義ファイルなのでパス Exit; end else if SameText(Key, 'surfacefile') then begin Result := Val; end else if SameText(Key, 'surface') or (Length(Key) = 0) then begin // サーフィス Dat := TStringList.Create; try Dat.CommaText := Val; for j := 0 to Dat.Count-1 do begin if System.Pos(':', Dat[j]) <= 0 then Continue; SurStr := Copy(Dat[j], 1, System.Pos(':', Dat[j])-1); PosStr := Copy(Dat[j], System.Pos(':', Dat[j])+1, High(integer)); try if System.Pos('-', SurStr) > 0 then begin smin := StrToInt(Copy(SurStr, 1, System.Pos('-', SurStr)-1)); smax := StrToInt(Copy(SurStr, System.Pos('-', SurStr)+1, High(integer))); end else begin smin := StrToInt(SurStr); smax := smin; end; for k := smin to smax do begin if PosStr = '*' then begin Sur2Pos.Values[IntToStr(k)] := IntToStr(k); oldsur := k; end else if PosStr = '-2' then begin Sur2Pos.Values[IntToStr(k)] := '' // 定義解除 end else if PosStr = '+' then begin Inc(oldsur); Sur2Pos.Values[IntToStr(k)] := IntToStr(oldsur); end else if StrToInt(PosStr) >= 0 then begin Sur2Pos.Values[IntToStr(k)] := PosStr; oldsur := StrToInt(PosStr); end; end; except Continue; end; end; finally Dat.Free; end; end; end; // ShowMessage(Sur2Pos.Text); if SakuraName <> Ghost then //sakura=??の指定が抜けている定義ファイルの場合 Result := '' else begin if Sur2Pos.Values[IntToStr(Surface)] <> '' then Pos := StrToInt(Sur2Pos.Values[IntToStr(Surface)]) else Result := ''; end; finally Sur2Pos.Free; end; end; function LoadDefinitionFile(Ghost: String; Surface: integer; FileName: String; out Pos: integer): String; var Lines: TStringList; begin Lines := TStringList.Create; Lines.LoadFromFile(FileName); try if System.Pos('SVG3', Lines[0]) > 0 then Result := LoadDefinitionFileVer3(Ghost, Surface, Lines, FileName, Pos) else Result := LoadDefinitionFileVer2(Ghost, Surface, Lines, FileName, Pos); finally Lines.Free; end; end; function FetchBmpFileAndPosition(Ghost: String; Surface: integer; out Pos: integer): String; var Ghosts, AGhost: TStringList; i, dum: integer; Dir, DefFileName, BmpFileName: String; begin Result := ''; if Ghost2File.Values[Ghost] <> '' then // すでにそのゴーストについて調査済み begin DefFileName := Ghost2File.Values[Ghost]; if DefFileName = '*' then // そのゴーストは知らない、と記憶している begin Exit; end else // そのゴーストを知っている begin BmpFileName := LoadDefinitionFile(Ghost, Surface, DefFileName, dum); if BmpFileName <> '' then begin Result := ExtractFilePath(DefFileName) + BmpFileName; Pos := dum; Exit; end; end; end; // ゴーストが見つからなかった結果を記憶。 // 見つかった場合にはファイル名で上書きされる if Result = '' then Ghost2File.Values[Ghost] := '*'; // 内部関数。ghost.txtを読み込む。 Dir := ExtractFilePath(GhostFile); Ghosts := TStringList.Create; try Ghosts.LoadFromFile(GhostFile); AGhost := TStringList.Create; try for i := 0 to Ghosts.Count-1 do begin AGhost.CommaText := Ghosts[i]; if AGhost[0] <> 'GHOST' then Continue; DefFileName := Dir + AGhost[3]; // 定義ファイルを読み込む BmpFileName := LoadDefinitionFile(Ghost, Surface, DefFileName, dum); if BmpFileName <> '' then begin Result := ExtractFilePath(DefFileName) + BmpFileName; Pos := dum; Break; end; end; finally AGhost.Free; end; finally Ghosts.Free; end; end; // Ghostで指定されるゴーストのSurface番のサーフィスイメージを実際に読み出す。 // Hで指定されているビットマップに書き出すこと。 function GetImage(Ghost: PChar; Surface: integer; H: HBITMAP): integer; cdecl; var Bmp: TBitmap; BmpFile: String; Position, x, y, nCol: integer; begin try BmpFile := FetchBmpFileAndPosition(Ghost, Surface, Position); if BmpFile = '' then // そのようなゴーストのそのようなサーフィスは見つからない begin Result := 1; Exit; end; Bmp := TBitmap.Create; try Bmp.Handle := H; if PicFileName <> BmpFile then begin try Pic.LoadFromFile(BmpFile); PicFileName := BmpFile; except // 作り直し FreeAndNil(Pic); Pic := TPicture.Create; PicFileName := ''; raise; end; end; nCol := Pic.Width div SurfaceWidth; x := SurfaceWidth * (Position mod nCol); y := SurfaceHeight * (Position div nCol); Bmp.Canvas.Draw(-x, -y, Pic.Graphic); finally Bmp.ReleaseHandle; Bmp.Free; end; Result := 0; except Result := 1; end; end; // イメージの大きさを返す。 // 大抵の場合イメージの大きさはもとから分かっていると思うので、 // それなら単純に定数を返して差し支えない。 // イメージの大きさがゴーストやサーフィス番号によって変わるならそのように。 function GetImageSize(Ghost: PChar; Surface: integer; var w, h: integer): integer; cdecl; begin w := SurfaceWidth; h := SurfaceHeight; Result := 0; end; // DLL固有の設定を行う。 // procedure Configure; cdecl; var OpenDialog: TOpenDialog; Ini: TIniFile; begin try ShowMessage('Specify SSTP Viewer''s ghost file.'); OpenDialog := TOpenDialog.Create(nil); try OpenDialog.Filter := 'Ghost Definition File(ghost.txt)|ghost.txt|' + 'All Files(*.*)|*.*'; OpenDialog.FileName := GhostFile; if OpenDialog.Execute then begin GhostFile := OpenDialog.FileName; Ini := TIniFile.Create(MyPath + ConfigFile); try Ini.WriteString('SVG', 'GhostFile', GhostFile); finally Ini.Free; end; Ghost2File.Clear; end; finally OpenDialog.Free; end; except on E: Exception do ShowMessage(E.Message); end; end; exports Load, Unload, GetVersion, Configure, GetImage, GetImageSize; begin end.