OSDN Git Service

IdAntiFreezeを貼り付けた (ToDo #347)
[winbottle/winbottle.git] / bottleclient / MainForm.pas
index 4fe6d3a..65448ff 100755 (executable)
@@ -1,5 +1,10 @@
 unit MainForm;
 
+{
+\83A\83v\83\8a\83P\81[\83V\83\87\83\93\82Ì\83\81\83C\83\93\83t\83H\81[\83\80\81B
+\91\97\90M\81E\8eó\90M\81E\83{\83g\83\8b\94z\91\97\8aÖ\8cW\82Ì\82¢\82ë\82¢\82ë\82È\8f\88\97\9d\82ð\8ds\82¤\81B
+}
+
 interface
 
 uses
@@ -8,14 +13,17 @@ uses
   DirectSstp, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
   IdSLPP20, SsParser, ImgList, AppEvnts, TaskTray, StdActns,
   ActnList, MPlayer, MenuBar, ToolWin,
-  IniFiles, ExtCtrls, ShellAPI,
-  ConstEditor, Buttons, Clipbrd, HeadValue, Logs,
-  IdException, HttpThread, IdHTTP, IdURI, LogDownload,
+  IniFiles, ExtCtrls, ShellAPI, Contnrs,
+  ConstEditor, Buttons, Clipbrd, HeadValue, Logs, MultipleChoiceEditor,
+  IdException, HttpThread, IdHTTP, LogDownload,
   ScriptConsts, DateUtils, BottleChainRule, BottleChainEvent,
-  SakuraSeekerInstance, HEditor, heClasses, heFountain,
-  SakuraScriptFountain;
+  SakuraSeekerInstance, HEditor, HTSearch, heClasses, heFountain,
+  SakuraScriptFountain, SppList, SurfacePreview, XDOM_2_3_J3, SsPlayTime,
+  RegexUtils, StrReplace, StrReplaceDialog, IdAntiFreezeBase, IdAntiFreeze;
 
 type
+  TSurfacePreviewType = (spHint, spEditor);
+
   TfrmSender = class(TForm)
     MainMenu: TMainMenu;
     mnFile: TMenuItem;
@@ -57,7 +65,6 @@ type
     mnSend: TMenuItem;
     mnConfirm: TMenuItem;
     mnClear: TMenuItem;
-    N9: TMenuItem;
     imgIcon: TImageList;
     mnPopupConst: TPopupMenu;
     actEditConst: TAction;
@@ -76,7 +83,6 @@ type
     ConstMenuBar: TMenuBar;
     mnGoToHP: TMenuItem;
     LabelTimer: TTimer;
-    mnColorScript: TMenuItem;
     mnCopyAll: TMenuItem;
     actCopyAll: TAction;
     actCopyAllNoReturn: TAction;
@@ -117,7 +123,6 @@ type
     mnLeaveThisChannel: TMenuItem;
     N4: TMenuItem;
     mnGotoVote: TMenuItem;
-    mnGotoGLog: TMenuItem;
     mnGoToHelp: TMenuItem;
     btnSend: TButton;
     btnConfirm: TButton;
@@ -146,7 +151,32 @@ type
     actPaste: TAction;
     actCut: TAction;
     actSelectAll: TAction;
+    actRecallScriptBuffer: TAction;
+    N5: TMenuItem;
+    mnRecallScriptBuffer: TMenuItem;
+    tbtnEditorPreview: TToolButton;
+    actEditorPreview: TAction;
+    mnEditorPreview: TMenuItem;
+    actResetPlugins: TAction;
+    N7: TMenuItem;
+    mnResetPlugins: TMenuItem;
+    actReplace: TAction;
+    N10: TMenuItem;
+    mnReplace: TMenuItem;
+    actSendToEditor: TAction;
+    actSendToLogWindow: TAction;
+    mnSendLogWindow: TMenuItem;
+    actDeleteLogItem: TAction;
+    actAbout: TAction;
     actEditCopy: TEditCopy;
+    tbtnSendToLogWindow: TToolButton;
+    SsPlayTime: TSsPlayTime;
+    actUndo: TAction;
+    actRedo: TAction;
+    mnUndo: TMenuItem;
+    mnRedo: TMenuItem;
+    N9: TMenuItem;
+    AntiFreeze: TIdAntiFreeze;
     procedure actConfirmExecute(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
@@ -156,7 +186,7 @@ type
     procedure actStartClick(Sender: TObject);
     procedure actStopExecute(Sender: TObject);
     procedure FormShow(Sender: TObject);
-    procedure mnAboutClick(Sender: TObject);
+    procedure actAboutClick(Sender: TObject);
     procedure actExitClientExecute(Sender: TObject);
     procedure actClearExecute(Sender: TObject);
     procedure memScriptChange(Sender: TObject);
@@ -192,16 +222,12 @@ type
     procedure actNextChannelExecute(Sender: TObject);
     procedure cbxTargetGhostDropDown(Sender: TObject);
     procedure actShowLogExecute(Sender: TObject);
-    procedure Slpp20Connect(Sender: TObject);
     procedure actSleepExecute(Sender: TObject);
-    procedure tabChannelDrawTab(Control: TCustomTabControl;
-      TabIndex: Integer; const Rect: TRect; Active: Boolean);
     procedure actVoteMessageExecute(Sender: TObject);
     procedure tabChannelContextPopup(Sender: TObject; MousePos: TPoint;
       var Handled: Boolean);
     procedure mnLeaveThisChannelClick(Sender: TObject);
     procedure mnGotoVoteClick(Sender: TObject);
-    procedure mnGotoGLogClick(Sender: TObject);
     procedure tabChannelMouseMove(Sender: TObject; Shift: TShiftState; X,
       Y: Integer);
     procedure mnGoToHelpClick(Sender: TObject);
@@ -228,38 +254,70 @@ type
     procedure actPasteExecute(Sender: TObject);
     procedure actCutExecute(Sender: TObject);
     procedure actSelectAllExecute(Sender: TObject);
+    procedure memScriptMouseMove(Sender: TObject; Shift: TShiftState; X,
+      Y: Integer);
+    procedure actRecallScriptBufferExecute(Sender: TObject);
+    procedure actEditorPreviewExecute(Sender: TObject);
+    procedure actResetPluginsExecute(Sender: TObject);
+    procedure IdSLPP20Connect(Sender: TObject);
+    procedure actReplaceExecute(Sender: TObject);
+    procedure actSendToEditorExecute(Sender: TObject);
+    procedure actSendToLogWindowExecute(Sender: TObject);
+    procedure memScriptDragOver(Sender, Source: TObject; X, Y: Integer;
+      State: TDragState; var Accept: Boolean);
+    procedure memScriptDragDrop(Sender, Source: TObject; X, Y: Integer);
+    procedure actDeleteLogItemExecute(Sender: TObject);
+    procedure memScriptSelectionChange(Sender: TObject; Selected: Boolean);
+    procedure actUndoExecute(Sender: TObject);
+    procedure actRedoExecute(Sender: TObject);
   private
-    FSleeping: boolean;
+    FSleeping: boolean;  // \94z\91\97\83X\83\8a\81[\83v\92\86\82©\82Ç\82¤\82©
     FStatusText: String;
     FConnecting: boolean;
     FAdded: boolean;
     FBooted: boolean; //\8f\89\89ñ\8bN\93®\92Ê\90M\97p
+    FEndSession: Boolean; // Windows\8fI\97¹\82ð\8c\9f\92m\82µ\82Ätrue\82É\82È\82é
     FOriginalCaption: String;
-    FAutoAddAfterGetChannel: boolean;
+    FAutoAddAfterGetChannel: boolean; //\83`\83\83\83\93\83l\83\8b\8eæ\93¾\8cã\82É\83_\83C\83A\83\8d\83O\82È\82µ\82É
+                                      //\83`\83\83\83\93\83l\83\8b\82É\8eQ\89Á\82·\82é\82©\82Ç\82¤\82©
     FConstDir: String;
-    //
-    FMutex: THandle; //Mutex\83I\83u\83W\83F\83N\83g\81c\93ñ\8fd\8bN\93®\96h\8e~\97p
+    FSppDir: String;
     //
     FNowChannel: String; //\8c»\8dÝ\91I\91ð\82³\82ê\82Ä\82¢\82é\83`\83\83\83\93\83l\83\8b
-    JoinChannelsBackup: TStringList; //
+    JoinChannelsBackup: TStringList; //\88ê\8e\9e\8eg\97p
     //
     FScriptModified: boolean; // \83X\83N\83\8a\83v\83g\82ª\95Ï\8dX\82³\82ê\82Ä\82¢\82é\82©\82Ç\82¤\82©\81B
-                              // \83\8d\81[\83J\83\8b\8am\94F\8b­\90§\97p\83t\83\89\83O\81B
-                              // TRichEdit.Modified\82Í\95Ê\82Ì\97p\93r\82Å\8eg\82Á\82Ä\82¢\82é\82Ì\82Å\8eg\82¦\82È\82¢
+                              // \83\8d\81[\83J\83\8b\8am\94F\8b­\90§\97p\83t\83\89\83O\81BTRichEdit.Modified\82Í
+                              //\95Ê\82Ì\97p\93r\82Å\8eg\82Á\82Ä\82¢\82é\82Ì\82Å\8eg\82¦\82È\82¢
     //
     FDragTabIndex: integer; //\83^\83u\83h\83\89\83b\83O\83h\83\8d\83b\83v\8aÖ\98A
     FDragTabDest: integer;  //\83h\83\8d\83b\83v\82·\82é\88Ê\92u(\82·\82®\89E\82É\82­\82é\83^\83u\82Ì\83C\83\93\83f\83b\83N\83X)
     //
-    FBottleSstp: TBottleSstp; // \83X\83\8c\83b\83h\94Å\8dÄ\91\97\83v\83\8d\83O\83\89\83\80
+    FBottleSstp: TBottleSstp; // \8dÄ\91\97\83v\83\8d\83O\83\89\83\80
     //
     FHttp: THTTPDownloadThread; //HTTP\83_\83E\83\93\83\8d\81[\83h\83X\83\8c\83b\83h(\83C\83\93\83X\83^\83\93\83X\82Í1\8cÂ\82Ì\82Ý)
     FBeginConnectFailCount: integer; //\89½\93x\82à\90Ú\91±\8e¸\94s\82µ\82½\82ç\83\8a\83g\83\89\83C\92\86\8e~
+    //
+    FVisiblePreviewGhost: String;
+    FVisiblePreviewSurface: integer; //\83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[\82Å\8c©\82¦\82Ä\82¢\82é\82à\82Ì
+    FVisibleMenuItem: TMenuItem; //\83N\83\8a\83b\83N\82³\82ê\82½\83\81\83j\83\85\81[\83A\83C\83e\83\80\81B
+                                 //\83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[\82ª\83\81\83j\83\85\81[\82Ì\89º\82É
+                                 //\89B\82ê\82È\82¢\82æ\82¤\82É\95\\8e¦\82³\82ê\82Ä\82¢\82é\82à\82Ì\82ð\8bL\89¯\82µ\82Ä\82¨\82­
+    //
+    FTargetGhostExpand: boolean; //\83S\81[\83X\83g\91I\91ð\83{\83b\83N\83X\82ª\81A
+                                 //\88ê\8e\9e\93I\82É\91S\95\94\95\\8e¦\82³\82ê\82Ä\82¢\82é\82©\82Ç\82¤\82©\82Ì\83t\83\89\83O
+    //
+    FScriptBuffer: TObjectList;  //\83X\83N\83\8a\83v\83g\83N\83\8a\83A\83o\83b\83t\83@
+    //
+    FWM_TaskBarCreated: WORD; // \83^\83X\83N\83o\81[\93o\98^\97p\83E\83B\83\93\83h\83E\83\81\83b\83Z\81[\83WID
+    //
     procedure SetStatusText(const Value: String);
     procedure SetSleeping(const Value: boolean);
     procedure YenETrans;
     procedure SetConnecting(const Value: boolean);
     procedure SetAdded(const Value: boolean);
     procedure mnConstClick(Sender: TObject);
+    procedure mnConstGroupClick(Sender: TObject);
     property Added: boolean read FAdded write SetAdded;
     property Sleeping: boolean read FSleeping write SetSleeping;
     property StatusText: String read FStatusText write SetStatusText;
@@ -274,22 +332,47 @@ type
     procedure NoLuidError;
     procedure UpdateIfGhostBox;
     function BuildMenuConditionCheck(const IfGhost, Ghost: String): boolean;
-    procedure BuildMenu(Root: TMenuItem; Event: TNotifyEvent; Simple: boolean);
+    procedure BuildMenu(Root: TMenuItem; Simple: boolean);
     procedure PlaySound(const FileName: String);
     //TBottleSstp\8aÖ\8cW\83C\83x\83\93\83g\83n\83\93\83h\83\89
     procedure BottleSstpResendCountChange(Sender: TObject);
     procedure BottleSstpResendTrying(Sender: TObject; MID: String);
     procedure BottleSstpResendEnd(Sender: TObject; MID: String);
+    function IsSurfaceTag(const Script: String; var ID: integer): boolean;
+    procedure DoSurfacePreview(Surface: integer; const Kind:
+      TSurfacePreviewType);
+    function GetSurfacePreviewPositionHint(w, h: integer): TPoint;
+    function GetSurfacePreviewPositionScriptPoint(w, h: integer): TPoint;
+    procedure EditorPreview;
+    // \83^\83O\82Ì\95\8e\9a\97ñ\82ð\95Ï\8a·\82·\82é
+    function TagReplace(Script: String;
+      Before, After: array of String): String; overload;
+    function TagReplace(Script: String;
+      Before, After: TStrings): String; overload;
+    // \83T\81[\83t\83B\83X\82ð\95Ï\8a·\82·\82é
+    function ReplaceSurface(Script: String; Params: TCollection): String;
+    procedure ClearEditor;
+    procedure CopyFromLogToEditor(Log: TLogItem);
+    //
+    procedure AppendTextLog(const FileName, Line: String);
+    procedure AppendXMLLog(const FileName: String; Args: THeadValue);
+  protected
+    procedure WndProc(var Message: TMessage); override;
+    procedure WMQueryEndSession(var msg: TWMQueryEndSession);
+      message WM_QUERYENDSESSION;
   public
     function DoTrans(var Script: String;
-      Options: TScriptTransOptions): String;
-    function ScriptTransForSSTP(const Script: String): String;
+      Options: TScriptTransOptions): String; overload;
+    function DoTrans(var Script: String;
+      Options: TScriptTransOptions; out FoundURL: boolean): String; overload;
+    function ScriptTransForSSTP(const Script: String;
+      out Error: String): String; overload;
     procedure BeginConnect;
     procedure RetryBeginConnect;
     procedure EndConnect;
     procedure ConstructMenu(Simple: boolean);
     property Connecting: boolean read FConnecting write SetConnecting;
-    function SetHWndToFavoriteGhost(const Ghost: String): String;
+    property BottleSstp: TBottleSstp read FBottleSstp;
     function GhostNameToSetName(const Ghost: String): String;
     procedure PostCommand(const Command: array of String); overload;
     procedure PostCommand(Command: TStrings); overload;
@@ -305,8 +388,8 @@ const
   PanelConnecting = 0;  //\81u\90Ú\91±\92\86\81v\95\\8e¦\97p
   PanelBytes      = 1;  //\81\9b\81\9b\83o\83C\83g
   PanelCount      = 2;  //Local Proxy\81A\8c»\8dÝ\81\9b\8c\8f\91Ò\82¿
-  PanelMembers    = 3;
-  PanelStatus     = 4;  //\93o\98^\82³\82ê\82Ä\82¢\82Ü\82·\81c\82È\82Ç
+  PanelMembers    = 3;  //\81\9b\81\9b\90l
+  PanelStatus     = 4;  //SSTP Bottle\83T\81[\83o\82É\93o\98^\82³\82ê\82Ä\82¢\82Ü\82·\81c\82È\82Ç
 
   IconConnected    = 17;
   IconDisconnected = 18;
@@ -320,10 +403,12 @@ const
 function Token(const Str: String; const Delimiter: char;
   const Index: integer): String;
 
+function StringReplaceEx(const Before: String; List: THeadValue): String;
+
 implementation
 
 uses SendConfirm, SettingForm, ChannelListForm, LogForm,
-  MessageBox, FMOExplorer;
+  MessageBox, FMOExplorer, EditorTalkShow;
 
 {$R *.DFM}
 
@@ -345,51 +430,110 @@ begin
   end;
 end;
 
+// \95\8e\9a\97ñ\82ð\92u\82«\8a·\82¦\82é\83\86\81[\83e\83B\83\8a\83e\83B\8aÖ\90\94
+function StringReplaceEx(const Before: String; List: THeadValue): String;
+var
+  i, MinPos, MinKey, p: integer;
+  Work: String;
+begin
+  Work := Before;
+  Result := '';
+  MinKey := -1;
+  while Work <> '' do
+  begin
+    MinPos := -1;
+    for i := 0 to List.Count-1 do
+    begin
+      p := Pos(List.KeyAt[i], Work);
+      if (p > 0) and ((p < MinPos) or (MinPos < 0)) then
+      begin
+        MinPos := p;
+        MinKey := i;
+      end;
+    end;
+    if MinPos < 0 then
+    begin
+      Result := Result + Work;
+      Break;
+    end else
+    begin
+      Result := Result + Copy(Work, 1, MinPos-1) + List.ValueAt[MinKey];
+      Delete(Work, 1, (MinPos - 1) + Length(List.KeyAt[MinKey]));
+    end;
+  end;
+end;
+
 {TfrmSender}
 
 procedure TfrmSender.actConfirmExecute(Sender: TObject);
-var Res: TSstpResult;
-    Script, Ghost, Err: String;
-    Opt: TScriptTransOptions;
+var
+  AScript, Err, AGhost: String;
+  Item: TLogItem;
+  Choice: integer;
 begin
-  if Length(GetScriptText) = 0 then Exit;
+  // Partial Confirmation
+  if memScript.SelText <> '' then
+  begin
+    Choice := 0;
+    if not Pref.AutoPartialConfirm then
+      if not MultipleChoiceEdit('\8am\94F', ['\91I\91ð\95\94\95ª\82Ì\82Ý', '\83X\83N\83\8a\83v\83g\91S\91Ì'], Choice) then
+        Exit;
+    if Choice = 0 then
+    begin
+      AScript := memScript.SelText;
+      AScript := StringReplace(Pref.PartialConfirmFormat, '|', AScript, []);
+    end else
+      AScript := GetScriptText;
+  end else
+    AScript := GetScriptText;
+  AScript := StringReplace(AScript, #13#10, '', [rfReplaceAll]);
+
+  if Length(AScript) = 0 then Exit;
   YenETrans;
-  Script := GetScriptText;
-  if Pref.IgnoreTimeCritical then
-    Opt := [toIgnoreTimeCritical, toWarnCheck]
-  else
-    Opt := [toWarnCheck];
-  if Pref.NoTransUrl then Opt := Opt + [toNoChoice];
-  if Pref.HUTagTo01Tag then Opt := Opt + [toHUTagTo01Tag];
-  Err := DoTrans(Script, Opt + [toConvertURL, toWarnMessySurface]);
-  if Err <> '' then begin
+  AScript := ScriptTransForSSTP(AScript, Err);
+  if Err <> '' then
+  begin
     ShowMessage(Err);
     Exit;
   end;
-  if cbxTargetGhost.ItemIndex > 0 then begin
-    Ghost := cbxTargetGhost.Text
-  end else begin
-    if FNowChannel <> '' then
-      Ghost := ChannelList.Channel[FNowChannel].Ghost;
-  end;
-  Ghost := SetHWndToFavoriteGhost(Ghost);
-  DirectSstp.SstpSender := 'SSTP Bottle -\81y\8am\94F\81z';
-  
-  Res := DirectSstp.SstpSEND(Script, [soNoTranslate], GhostNameToSetName(Ghost));
-  if Res <> srOk then begin
-    ShowHintLabel('\91\97\90M\8e¸\94s:' + DirectSstp.RecvLog, WarningColor);
-  end else begin
-    ShowHintLabel('');
-    FScriptModified := false;
+
+  if cbxTargetGhost.ItemIndex > 0 then
+    AGhost := cbxTargetGhost.Text
+  else if FNowChannel <> '' then
+    AGhost := ChannelList.Channel[FNowChannel].Ghost
+  else
+    AGhost := '';
+
+  if Pref.IgnoreTimeCritical then
+    AScript := TagReplace(AScript, ['\t'], ['']);
+
+  Item := TLogItem.Create;
+  try
+    with Item do
+    begin
+      LogType := ltBottle;
+      Script := AScript;
+      Channel := '\81y\8am\94F\81z';
+      Ghost := AGhost;
+    end;
+    BottleSstp.Unshift(Item);
+  except
+    Item.Free;
   end;
+
+  FScriptModified := false;
 end;
 
 procedure TfrmSender.FormCreate(Sender: TObject);
 var Str: TStringList;
 begin
+  FScriptBuffer := TObjectList.Create(true);
+
   SakuraSeeker.OnDetectResultChanged := SakuraSeekerDetectResultChanged;
   FConstDir := ExtractFileDir(Application.ExeName)+'\consts';
   ScriptConstList.LoadFromDir(FConstDir);
+  FSppDir := ExtractFileDir(Application.ExeName)+'\spp';
+  Spps.LoadFromDirectory(FSppDir);
   ConstructMenu(false);
 
   Str := TStringList.Create;
@@ -414,21 +558,6 @@ begin
 
   FOriginalCaption := Self.Caption;
 
-  {$IFDEF NOMUTEX}
-  ShowMessage('\93ñ\8fd\8bN\93®\8b\96\89Â\83o\81[\83W\83\87\83\93\82Å\82·\81B'#13#10 + VersionString);
-  {$ELSE}
-  FMutex := OpenMutex(MUTEX_ALL_ACCESS, false, 'SSTPBottleClient2');
-  if FMutex <> 0 then begin
-    Beep;
-    ShowMessage('SSTP Bottle Client\82Í\93ñ\8fd\8bN\93®\82Å\82«\82Ü\82¹\82ñ');
-    CloseHandle(FMutex);
-    Application.Terminate;
-    Application.ProcessMessages; //WM_QUIT\82ð\97¬\82·
-    Exit;
-  end;
-  FMutex := CreateMutex(nil, false, 'SSTPBottleClient2');
-  {$ENDIF}
-
   UpdateLayout;
   mnShowToolBar.Checked := Pref.ShowToolBar;
   mnShowConstBar.Checked := Pref.ShowConstBar;
@@ -440,13 +569,14 @@ begin
     mnStayOnTop.Checked := false;
   end;
 
+  // URL\83W\83\83\83\93\83v\90æ\82ð\83q\83\93\83g\95\8e\9a\97ñ\82Æ\82µ\82Ä\90Ý\92è
   mnGoToHP.Hint := Pref.HomePage;
-  mnGotoGlog.Hint := Pref.GLogPage;
   mnGotoVote.Hint := Pref.VotePage;
   mnGotoHelp.Hint := Pref.HelpPage;
 
   mnGetNewId.Enabled := (Pref.LUID = '');
 
+  // \82³\82­\82ç\83X\83N\83\8a\83v\83g\89ð\90Í\83p\83^\81[\83\93\82ð\93Ç\82Ý\8d\9e\82Ý
   try
     SsParser.TagPattern.LoadFromFile(ExtractFilePath(Application.Exename) + 'tagpat.txt');
     SsParser.MetaPattern.LoadFromFile(ExtractFilePath(Application.ExeName) + 'metapat.txt');
@@ -455,6 +585,10 @@ begin
     Application.Terminate;
   end;
 
+  // \8dÄ\90\8e\9e\8aÔ\90\84\92è\83R\83\93\83|\81[\83l\83\93\83g\82É\83p\83\89\83\81\81[\83^\82ð\8ew\92è
+  SsPlayTime.PlayTimeParams := Pref.PlayTimeParams;
+
+  // \83\81\83C\83\93\83E\83B\83\93\83h\83E\82Ì\88Ê\92u\82Æ\83T\83C\83Y\82ð\95\9c\8bA
   with Pref.SenderWindowPosition do begin
     Self.Left   := Left;
     Self.Top    := Top;
@@ -462,8 +596,14 @@ begin
     Self.Height := Bottom - Top + 1;
   end;
 
+  // \83^\83X\83N\83o\81[\82Ì\8dÄ\8bN\93®(Explorer\82ª\97\8e\82¿\82½\82Æ\82«)\82ð\8c\9f\8fo\82·\82é
+  FWM_TaskBarCreated := RegisterWindowMessage('TaskBarCreated');
+
+  // \83X\83N\83\8a\83v\83g\95\8e\9a\97ñ\82Ì\8f\89\8aú\89»
   actClearExecute(Sender);
+  // \83^\83X\83N\83g\83\8c\83C\82É\83A\83C\83R\83\93\82ð\92Ç\89Á
   ChangeTaskIcon;
+  // \83`\83\83\83\93\83l\83\8b\8eQ\89Á\8aÖ\8cW\82Ì\83^\83u\82Ì\8f\88\97\9d\82È\82Ç(\83`\83\83\83\93\83l\83\8b\95s\8eQ\89Á\82Å\8f\89\8aú\89»)
   UpdateJoinChannelList(nil);
 
   // SSTP\8dÄ\91\97\83I\83u\83W\83F\83N\83g
@@ -477,9 +617,11 @@ end;
 
 procedure TfrmSender.FormDestroy(Sender: TObject);
 begin
+  if FScriptBuffer <> nil then
+    FScriptBuffer.Free;
+
   if FBottleSstp <> nil then begin
     FBottleSstp.Terminate;
-    FBottleSstp.WaitFor;
     FBottleSstp.Free;
   end;
 
@@ -497,9 +639,6 @@ begin
   SaveChainRuleList;
   BottleChainRuleList.Free;
 
-  {$IFDEF BOTTLEMUTEX}
-  ReleaseMutex(FMutex);
-  {$ENDIF}
 end;
 
 
@@ -520,14 +659,13 @@ begin
     actStart.Enabled := true;
     actStop.Enabled := true;
     actSend.Enabled := true;
-    //actVoteMessage.Enabled := true;
-    //actAgreeMessage.Enabled := true;
     frmLog.lvwLogChange(Self, nil, ctState);
     mnGetNewId.Enabled := Pref.LUID = '';
     Screen.Cursor := crDefault;
   end;
 end;
 
+// \83\81\83b\83Z\81[\83W\91\97\90M
 procedure TfrmSender.actSendExecute(Sender: TObject);
 var Talk, Ghost: String;
     Command: TStringList;
@@ -554,7 +692,16 @@ begin
     Exit;
   end;
 
-  if Pref.NeedConfirmBeforeSend and FScriptModified then begin
+  YenETrans;
+  Talk := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
+  Err := DoTrans(Talk, [toWarnMessySurface, toWarnCheck]);
+  if Err <> '' then begin
+    MessageDlg(Err, mtWarning, [mbOk], 0);
+    Exit;
+  end;
+
+  if Pref.NeedConfirmBeforeSend and FScriptModified then
+  begin
     MessageDlg('\91\97\90M\91O\82É\83\8d\81[\83J\83\8b\8am\94F\82µ\82Ä\82­\82¾\82³\82¢\81B', mtError, [mbOk], 0);
     Exit;
   end;
@@ -563,18 +710,10 @@ begin
     if not SendConfirmDialog(FNowChannel, cbxTargetGhost.Text) then Exit;
   end;
 
-  YenETrans;
-  Talk := GetScriptText;
-  Err := DoTrans(Talk, [toWarnMessySurface, toWarnCheck]);
-  if Err <> '' then begin
-    MessageDlg(Err, mtWarning, [mbOk], 0);
-    Exit;
-  end;
-  Command := nil;
   Ghost := '';
   if cbxTargetGhost.ItemIndex > 0 then Ghost := cbxTargetGhost.Text;
+  Command := TStringList.Create;
   try
-    Command := TStringList.Create;
     with Command do begin
       Add('Command: sendBroadcast');
       Add('Channel: ' + FNowChannel);
@@ -614,8 +753,8 @@ begin
       IdSlpp20.Port := Pref.ProxyPort;
       IdSlpp20.ProxyMode := true;
     end else begin
-      IdSlpp20.Host := 'bottle.mikage.to';
-      IdSlpp20.Port := 9871;
+      IdSlpp20.Host := Pref.BottleServer;
+      IdSlpp20.Port := Pref.BottleServerPort;
       IdSlpp20.ProxyMode := false;
     end;
     IdSlpp20.Connect;
@@ -653,7 +792,6 @@ begin
   if Value then begin
     StatusText := 'SSTP Bottle \83T\81[\83o\82É\90Ú\91±\82³\82ê\82Ä\82¢\82Ü\82·';
     ChangeTaskIcon;
-    ShowHintLabel('SSTP Bottle\83T\81[\83o\82ª\8c©\82Â\82©\82è\82Ü\82µ\82½');
   end else begin
     StatusText := '\83T\81[\83o\82Æ\82Ì\90Ú\91±\82ª\90Ø\82ê\82Ä\82¢\82Ü\82·!';
     ChangeTaskIcon;
@@ -694,7 +832,7 @@ begin
                     '\90l\82É\91\97\90M\82µ\82Ü\82µ\82½');
       //\83S\81[\83X\83g\82ð\83f\83t\83H\83\8b\83g\82É\96ß\82·
       if Pref.ResetIfGhostAfterSend then begin
-        cbxTargetGhost.ItemIndex := 0;
+        actResetGhostExecute(Self);
       end;
       //\83X\83N\83\8a\83v\83g\82ð\83N\83\8a\83A
       if Pref.ClearAfterSend then begin
@@ -707,6 +845,12 @@ begin
       if ResStr = 'OK' then begin
         Pref.LUID := HeadValue['NewID'];
         ShowHintLabel('LUID\8eæ\93¾\8a®\97¹\81B');
+        frmMessageBox.ShowMessage('\8f\89\89ñ\8bN\93®\82Ì\82½\82ß\81A' +
+          'SSTP Bottle\83T\81[\83o\90Ú\91±\97p\82ÌID(LUID)\82ð\90V\8bK\8eæ\93¾\82µ\82Ü\82µ\82½\81B'#13#10 +
+          'LUID: ' + Pref.LUID +  #13#10 +
+          '\90Ý\92è\83t\83@\83C\83\8b\82ð\8dí\8f\9c\82·\82é\82±\82Æ\82Å\81ALUID\82Í\8e©\97R\82É\8eæ\93¾\82Å\82«\82Ü\82·\82ª\81A' +
+          'LUID\82Ì\8eg\97p\8eÀ\90Ñ\82É\89\9e\82\82Ä\93Á\93T\82ª\82 \82é\82©\82à\82µ\82ê\82Ü\82¹\82ñ\82Ì\82Å\81A' +
+          '\8fo\97\88\82é\82¾\82¯\93¯\82\82à\82Ì\82ð\8eg\82Á\82Ä\82­\82¾\82³\82¢\81B\8fÚ\8d×\82Í\83w\83\8b\83v\82ð\82²\97\97\82­\82¾\82³\82¢\81B');
         mnGetNewId.Enabled := false;
         BeginConnect;
       end else begin
@@ -735,9 +879,14 @@ begin
             end;
           end;
         end else begin
-          if frmChannelList.Execute(ChannelList, JoinChannels) then begin
-            SetChannel := TStringList.Create;
-            SetChannel.Assign(frmChannelList.JoinList);
+          Application.CreateForm(TfrmChannelList, frmChannelList);
+          try
+            if frmChannelList.Execute(ChannelList, JoinChannels) then begin
+              SetChannel := TStringList.Create;
+              SetChannel.Assign(frmChannelList.JoinList);
+            end;
+          finally
+            frmChannelList.Release;
           end;
         end;
         if SetChannel <> nil then PostSetChannel(SetChannel);
@@ -793,11 +942,17 @@ begin
   FAutoAddAfterGetChannel := Pref.AutoStart;
   FBooted := true;
   frmLog.Show;
+  frmSurfacePreview.Show;
   Self.Show;
-  SakuraSeekerDetectResultChanged(Self);
+  SakuraSeeker.BeginDetect;
+  SakuraSeekerDetectResultChanged(self);
+  if SakuraSeeker.Count = 0 then
+    frmMessageBox.ShowMessage('\83S\81[\83X\83g(SSTP\83T\81[\83o)\82ª1\82Â\82à\8bN\93®\82µ\82Ä\82¢\82Ü\82¹\82ñ\81B'#13#10 +
+      'SSTP Bottle\82ð\97\98\97p\82·\82é\82½\82ß\82É\82Í\81A\83S\81[\83X\83g\82ð\93¯\8e\9e\82É\8bN\93®\82µ\82Ä\82­\82¾\82³\82¢\81B'#13#10 +
+      '\8fÚ\8d×\82Í\83w\83\8b\83v\82ð\82²\97\97\89º\82³\82¢\81B');
 end;
 
-procedure TfrmSender.mnAboutClick(Sender: TObject);
+procedure TfrmSender.actAboutClick(Sender: TObject);
 var Str: String;
 begin
   Str := VersionString + #13#10 + BottleDisclaimer + #13#10#13#10;
@@ -811,28 +966,29 @@ begin
 end;
 
 procedure TfrmSender.actClearExecute(Sender: TObject);
-var TmpScript: String;
-    Position: Integer;
+var
+  Script, Default: String;
 begin
-  TmpScript := Pref.DefaultScript;
-  Position := Pos('|', TmpScript);
-  if Position < 1 then Position := 1;
-  TmpScript := StringReplace(TmpScript, '|', '', []);
-  memScript.Lines.Text := TmpScript;
-  Sendmessage(memScript.Handle, WM_VSCROLL, SB_LINEUP, 0);
-  memScript.SelStart := Position-1;
+  Script := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
+  Default := StringReplace(Pref.DefaultScript, '|', '', [rfReplaceAll]);
+  Default := StringReplace(Default, #13#10, '', [rfReplaceAll]);
 
-  if Visible then memScript.SetFocus;
-  FScriptModified := false;
-  memScriptChange(self);
+  if (Pref.AutoClip) and (Length(GetScriptText) > 0) and (Script <> Default) then
+    actSendToLogWindow.Execute
+  else
+    ClearEditor;
 end;
 
 procedure TfrmSender.memScriptChange(Sender: TObject);
-var Script: String;
+var
+  Script: String;
+  Text: String;
 begin
   Script := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
-  StatusBar.Panels[PanelBytes].Text := IntToStr(length(Script)) + '\83o\83C\83g';
+  Text := Format('%d\83o\83C\83g/%d\95b', [Length(Script), SsPlayTime.PlayTime(Script) div 1000]);
+  StatusBar.Panels[PanelBytes].Text := Text;
   FScriptModified := true;
+  EditorPreview;
 end;
 
 procedure TfrmSender.mnStayOnTopClick(Sender: TObject);
@@ -881,6 +1037,7 @@ end;
 procedure TfrmSender.FormClose(Sender: TObject; var Action: TCloseAction);
 begin
   EndConnect;
+  TaskTray.Registered := false;
 end;
 
 procedure TfrmSender.ApplicationEventsMinimize(Sender: TObject);
@@ -927,13 +1084,19 @@ begin
     if Sleeping then IcoNum := IconSleepDisconnected
     else IcoNum := IconDisconnected;
   end;
-  Ico := TIcon.Create;
   try
-    imgIcon.GetIcon(IcoNum, Ico);
-    TaskTray.Icon := Ico;
-    TaskTray.Registered := true;
-  finally
-    Ico.Free;
+    Ico := TIcon.Create;
+    try
+      imgIcon.GetIcon(IcoNum, Ico);
+      TaskTray.Icon := Ico;
+      TaskTray.Registered := true;
+    finally
+      Ico.Free;
+    end;
+  except
+    ; // PC\82Ì\95\89\89×\82ª\8d\82\82¢\82Æ4\95b\88È\93à\82É\83^\83X\83N\83g\83\8c\83C\93o\98^\82Å\82«\82¸\81A
+      // \83V\83F\83\8b\82ª\83n\83\93\83O\82µ\82Ä\82¢\82é\82Æ\94»\92f\82³\82ê\82Ä\97á\8aO\82ª\94­\90\82·\82é\81B
+      // \82»\82Ì\8fê\8d\87\82Í\83G\83\89\81[\82ð\96³\8e\8b\82·\82é
   end;
 end;
 
@@ -944,25 +1107,36 @@ begin
 end;
 
 procedure TfrmSender.ApplicationEventsHint(Sender: TObject);
+var id: integer;
 begin
-  if Length(Application.Hint) > 0 then begin
+  if Length(Application.Hint) > 0 then
+  begin
     StatusBar.Panels[PanelStatus].Text := GetLongHint(Application.Hint);
     Application.HintColor := clInfoBk;
     if (Application.Hint = SendButtonLongHint)
-    and (FNowChannel <> '') then begin
+    and (FNowChannel <> '') then
+    begin
       //\91\97\90M\83{\83^\83\93\82Ì\8fê\8d\87\82Í\91¬\8dU\8fo\82·
       Application.HintColor := clYellow;
       Application.ActivateHint(Mouse.CursorPos);
     end;
+    if IsSurfaceTag(Application.Hint, id) and Pref.SurfacePreviewOnHint then
+    begin
+      // \83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[
+      DoSurfacePreview(id, spHint);
+    end;
   end else
+  begin
     StatusBar.Panels[PanelStatus].Text := FStatusText;
+    frmSurfacePreview.HideAway;
+  end;
 end;
 
 procedure TfrmSender.ConstructMenu(Simple: boolean);
 begin
-  BuildMenu(mnPopConst, mnConstClick, Simple);
-  BuildMenu(mnPopUpConst.Items, mnConstClick, Simple);
-  BuildMenu(ConstBarMenu.Items, mnConstClick, Simple);
+  BuildMenu(mnPopConst, Simple);
+  BuildMenu(mnPopUpConst.Items, Simple);
+  BuildMenu(ConstBarMenu.Items, Simple);
   //ConstMenuBar.Menu := nil;
   ConstMenuBar.AutoSize := false;
   ConstMenuBar.Width := 1000;
@@ -1026,7 +1200,7 @@ begin
     SynchronizedColor.Color := Pref.TalkColorS;
   end;
   memScript.Ruler.Visible := Pref.ShowRuler;
-  memScript.Ruler.Color := Pref.TalkColorH;
+  memScript.Ruler.Color := Pref.TextColor;
   memScript.Color := Pref.BgColor;
 
   ToolBar.Visible := Pref.ShowToolBar;
@@ -1039,12 +1213,13 @@ begin
       tpTop:    Align  := alTop;
       tpBottom: Align := alBottom;
     end;
+    TabWidth := Pref.TabWidth;
   end;
 end;
 
 function TfrmSender.DoTrans(var Script: String;
-  Options: TScriptTransOptions): String;
-var Orig, UrlCancel, Mark: String;
+  Options: TScriptTransOptions; out FoundURL: boolean): String;
+var UrlCancel, Mark: String;
     Url, UrlName: array[0..6] of String;
     i, j, u, UrlCount: integer;
     LastSurfaceH, LastSurfaceU: integer;
@@ -1058,7 +1233,8 @@ begin
   UnyuTalking := false;
   QuickSection := false;
   Synchronize := false;
-  Orig := SsParser.InputString;
+  SsParser.LeaveEscape := true;
+  SsParser.EscapeInvalidMeta := false;
   SsParser.InputString := Script;
   Script := '';
   Warnings := TStringList.Create;
@@ -1135,6 +1311,7 @@ begin
       end;
     end;
     if UrlCount > 0 then begin
+      FoundUrl := true;
       Script := Script + '\h\n';
       if not (toNoChoice in Options) then begin
         for i := 0 to UrlCount-1 do begin
@@ -1147,10 +1324,11 @@ begin
         Script := Script + '\h';
         for i := 0 to UrlCount-1 do begin
           Script := Script + Format('\n{%s}(%s)', [UrlName[i], Url[i]]);
-          Script := Script + Format('\n{%s}', [UrlCancel]);
         end;
+        Script := Script + Format('\n{%s}', [UrlCancel]);
       end;
-    end;
+    end else
+      FoundUrl := false;
     //\83X\83N\83\8a\83v\83g\82Ì\8dÅ\8cã\82É\83E\83F\83C\83g\91}\93ü
     if toWaitScriptEnd in Options then begin
       i := Pref.WaitScriptEnd;
@@ -1189,8 +1367,13 @@ begin
   finally
     Warnings.Free;
   end;
+end;
 
-  SsParser.InputString := Orig;
+function TfrmSender.DoTrans(var Script: String;
+  Options: TScriptTransOptions): String;
+var dum: boolean;
+begin
+  Result := DoTrans(Script, Options, dum);
 end;
 
 procedure TfrmSender.mnGoToHPClick(Sender: TObject);
@@ -1236,13 +1419,11 @@ procedure TfrmSender.Slpp20SlppEvent(Sender: TObject; EventType: TIdSlppEventTyp
   const Param: String);
 var HeadValue: THeadValue;
 begin
-  HeadValue := nil;
+  HeadValue := THeadValue.Create(Param);
   try
-    HeadValue := THeadValue.Create(Param);
     case EventType of
       etScript, etForceBroadcast, etUnicast: begin
         //\83\81\83b\83Z\81[\83W\8eó\90M
-        HeadValue := THeadValue.Create(Param);
         DispatchBottle(EventType, HeadValue);
       end;
       etMemberCount: begin
@@ -1256,15 +1437,12 @@ begin
       end;
       etConnectOk: begin
         ShowHintLabel('SSTP Bottle\83T\81[\83o\82Æ\92Ê\90M\8am\97§\81B');
+        Added := true;
         FBeginConnectFailCount := 0;
         //\83`\83\83\83\93\83l\83\8b\8e©\93®\93o\98^
         if not Connecting then
           PostCommand(['Command: getChannels']);
         SakuraSeeker.BeginDetect;
-        if SakuraSeeker.Count = 0 then
-          frmMessageBox.ShowMessage('\83S\81[\83X\83g(SSTP\83T\81[\83o)\82ª1\82Â\82à\8bN\93®\82µ\82Ä\82¢\82Ü\82¹\82ñ\81B'#13#10 +
-            'SSTP Bottle\82ð\97\98\97p\82·\82é\82½\82ß\82É\82Í\81A\83S\81[\83X\83g\82ð\93¯\8e\9e\82É\8bN\93®\82·\82é\95K\97v\82ª\82 \82è\82Ü\82·'#13#10 +
-            '\8fÚ\8d×\82Í\83w\83\8b\83v\82ð\82²\97\97\89º\82³\82¢\81B');
       end;
       etChannelList: begin
         UpdateJoinChannelList(HeadValue);
@@ -1303,8 +1481,12 @@ end;
 procedure TfrmSender.BottleSstpResendCountChange(Sender: TObject);
 begin
   StatusBar.Panels[PanelCount].Text := IntToStr(FBottleSstp.CueCount) + '\8c\8f';
-  TaskTray.TipString := 'SSTP Bottle Client (' +
-                        IntToStr(FBottleSstp.CueCount) + '\8c\8f)';
+  try
+    TaskTray.TipString := 'SSTP Bottle Client (' +
+                          IntToStr(FBottleSstp.CueCount) + '\8c\8f)';
+  except
+    ; // \83^\83X\83N\83g\83\8c\83C\93o\98^\8e¸\94s\82ð\96³\8e\8b\82·\82é
+  end;
   actClearBottles.Enabled := (FBottleSstp.CueCount > 0);
 end;
 
@@ -1358,33 +1540,8 @@ begin
 end;
 
 procedure TfrmSender.SakuraSeekerDetectResultChanged(Sender: TObject);
-var i: integer;
-    GhostList: String;
-    Http: THTTPDownloadThread;
-    SendOk: boolean;
 begin
   UpdateIfGhostBox; // \83h\83\8d\83b\83v\83_\83E\83\93\82Ì\92\86\90g\82ð\8f\91\82«\8a·\82¦\82é
-  //\8d\91\90¨\92²\8d¸\82É\8eQ\89Á
-  if FBooted and not Pref.NoSendGhostList and (SakuraSeeker.Count > 0) then begin
-    GhostList := 'CCC=' + TIdURI.ParamsEncode('\88¤');
-    GhostList := GhostList + '&LUID=' + Pref.LUID;
-    SendOk := false;
-    for i := 0 to SakuraSeeker.Count-1 do begin
-      if SakuraSeeker[i].Name <> '' then begin//\82±\82ê\82ª\82È\82¢\82Æ\82½\82Ü\82ÉFMO\89ó\82ê\82Å\8bó\82Ì\83S\81[\83X\83g\82ð\91\97\82Á\82Ä\82µ\82Ü\82¤
-        GhostList := GhostList + '&GHOST=' + TIdURI.ParamsEncode(SakuraSeeker[i].SetName);
-        SendOk := true;
-      end;
-    end;
-    if SendOk then begin
-      Http := THTTPDownloadThread.Create(BottleServer, Pref.CgiNameGhost, GhostList);
-      if Pref.UseHttpProxy then begin
-        Http.ProxyServer := Pref.ProxyAddress;
-        Http.ProxyPort   := Pref.ProxyPort;
-      end;
-      Http.FreeOnTerminate := true;
-      Http.Resume;
-    end;
-  end;
 end;
 
 procedure TfrmSender.UpdateChannelInfo(Dat: THeadValue);
@@ -1474,6 +1631,8 @@ begin
         Tabs.Add(JoinChannels[i]);
     end;
     Tabs.EndUpdate;
+    // \8c³\82©\82ç\83`\83\83\83\93\83l\83\8b\82É\8eQ\89Á\82µ\82Ä\82¢\82½\8fê\8d\87\82Í
+    // \91I\91ð\82³\82ê\82Ä\82¢\82½\83`\83\83\83\93\83l\83\8b\82ª\95Ï\82í\82ç\82È\82¢\82æ\82¤\82É\82·\82é(\83^\83u\82ª\82¸\82ê\82È\82¢\8f\88\97\9d)
     TabIndex := 0;
     for i := 0 to Tabs.Count-1 do
       if Tabs[i] = FNowChannel then TabIndex := i;
@@ -1516,11 +1675,6 @@ begin
   if frmLog.WindowState = wsMinimized then frmLog.WindowState := wsNormal;
 end;
 
-procedure TfrmSender.Slpp20Connect(Sender: TObject);
-begin
-  Added := true;
-end;
-
 procedure TfrmSender.actSleepExecute(Sender: TObject);
 begin
   if actSleep.Checked then begin
@@ -1539,7 +1693,7 @@ procedure TfrmSender.DispatchBottle(EventType: TIdSlppEventType;
   Dat: THeadValue);
 var Opt: TSstpSendOptions;
     Event: TBottleChainBottleEvent;
-    Script, Sender, Ghost, Channel: String;
+    Script, Sender, Ghost, Channel, ErrorMes: String;
     BreakFlag, NoDispatch: boolean;
     Sound, LogName: String;
     i, j, k, SkipCount: integer;
@@ -1547,6 +1701,7 @@ var Opt: TSstpSendOptions;
     Action: TBottleChainAction;
     LogNameList: TStringList;
     CueItem: TLogItem;
+    ReplaceHash: THeadValue; 
 begin
   Opt := [];
   if Pref.NoTranslate then Opt := Opt + [soNoTranslate];
@@ -1567,108 +1722,120 @@ begin
   end;
   Dat['TargetGhost'] := Ghost;
 
-  Event := TBottleChainBottleEvent.Create;
-  Event.Data := Dat;
-  if EventType = etScript then Event.LogType := ltBottle
-  else Event.LogType := ltSystemLog;
-
-  //\83X\83N\83\8a\83v\83g\95Ï\8a·
-  Script := ScriptTransForSSTP(Dat['Script']);
-  if Script = '' then begin
-    frmLog.AddCurrentSystemLog('SYSTEM', '\96â\91è\82Ì\82 \82é\89Â\94\\90«\82Ì\82 \82é\83X\83N\83\8a\83v\83g\82ª\93Í\82¢\82½\82½\82ß\81A'+
-                  '\94z\91\97\82³\82ê\82Ü\82¹\82ñ\82Å\82µ\82½\81@\81c '+Dat['Script']);
-    Exit;
-  end;
+  // \83\81\83^\95\8e\9a\8f\80\94õ
+  ReplaceHash := THeadValue.Create;
+  ReplaceHash['%channel%'] := SafeFileName(Dat['Channel']);
+  ReplaceHash['%ghost%'] := SafeFileName(Dat['IfGhost']);
+  ReplaceHash['%date%'] := FormatDateTime('yy-mm-dd', Now());
+  ReplaceHash['%year%'] := FormatDateTime('yyyy', Now());
+  ReplaceHash['%yy%'] := FormatDateTime('yy', Now());
+  ReplaceHash['%month%'] := FormatDateTime('mm', Now());
+  ReplaceHash['%day%'] := FormatDateTime('dd', Now());
+  ReplaceHash['%hour%'] := FormatDateTime('hh', Now());
+  ReplaceHash['%minute%'] := FormatDateTime('nn', Now());
 
-  BreakFlag := false;
-  NoDispatch := false;
-  Sound := '';
-  LogNameList := TStringList.Create;
-  SkipCount := 0;
+  Event := TBottleChainBottleEvent.Create;
   try
-    for i := 0 to BottleChainRuleList.Count-1 do begin
-      if SkipCount > 0 then begin
-        Dec(SkipCount);
-        Continue;
-      end;
-      Rule := BottleChainRuleList[i];
-      if not Rule.Enabled then Continue;
-      if not Rule.Check(Event) then Continue;
-      for j := 0 to Rule.Actions.Count-1 do begin
-        Action := (Rule.Actions[j] as TBottleChainAction);
-        if Action is TBottleChainAbortRuleAction then BreakFlag := true;
-        if Action is TBottleChainSkipRuleAction then
-          SkipCount := (Action as TBottleChainSkipRuleAction).SkipCount;
-        if (Action is TBottleChainSoundAction) and (Sound = '') then begin
-          Sound := (Action as TBottleChainSoundAction).SoundFile;
-          Sound := StringReplace(Sound, '%channel%', Dat['Channel'], [rfReplaceAll]);
-          Sound := StringReplace(Sound, '%ghost%', Dat['TargetGhost'], [rfReplaceAll]);
+    Event.Data := Dat;
+    if EventType = etScript then Event.LogType := ltBottle
+    else Event.LogType := ltSystemLog;
+
+    //\83X\83N\83\8a\83v\83g\95Ï\8a·
+    Script := ScriptTransForSSTP(Dat['Script'], ErrorMes);
+    if ErrorMes <> '' then begin
+      frmLog.AddCurrentSystemLog('SYSTEM', '\96â\91è\82Ì\82 \82é\89Â\94\\90«\82Ì\82 \82é\83X\83N\83\8a\83v\83g\82ª\93Í\82¢\82½\82½\82ß\81A'+
+                    '\94z\91\97\82³\82ê\82Ü\82¹\82ñ\82Å\82µ\82½\81@\81c '+Dat['Script']);
+      Exit;
+    end;
+
+    BreakFlag := false;
+    NoDispatch := false;
+    Sound := '';
+    LogNameList := TStringList.Create;
+    SkipCount := 0;
+    try
+      for i := 0 to BottleChainRuleList.Count-1 do begin
+        if SkipCount > 0 then begin
+          Dec(SkipCount);
+          Continue;
         end;
-        if Action is TBottleChainNoDispatchAction then NoDispatch := true;
-        if Action is TBottleChainLogAction then begin
-          for k := 0 to (Action as TBottleChainLogAction).LogNames.Count-1 do begin
-            LogName := (Action as TBottleChainLogAction).LogNames[k];
-            LogName := StringReplace(LogName, '%channel%', Dat['Channel'], [rfReplaceAll]);
-            LogName := StringReplace(LogName, '%ghost%', Dat['TargetGhost'], [rfReplaceAll]);
-            LogName := StringReplace(LogName, '%date%', FormatDateTime('yy/mm/dd', Now()), [rfReplaceAll]);
-            LogNameList.Add(LogName);
+        Rule := BottleChainRuleList[i];
+        if not Rule.Enabled then Continue;
+        if not Rule.Check(Event) then Continue;
+        for j := 0 to Rule.Actions.Count-1 do begin
+          Action := (Rule.Actions[j] as TBottleChainAction);
+          if Action is TBottleChainAbortRuleAction then
+            BreakFlag := true;
+          if Action is TBottleChainSkipRuleAction then
+            SkipCount := (Action as TBottleChainSkipRuleAction).SkipCount;
+          if (Action is TBottleChainSoundAction) and (Sound = '') then
+          begin
+            Sound := (Action as TBottleChainSoundAction).SoundFile;
+            Sound := StringReplaceEx(Sound, ReplaceHash);
           end;
+          if Action is TBottleChainNoDispatchAction then
+            NoDispatch := true;
+          if Action is TBottleChainLogAction then
+          begin
+            for k := 0 to (Action as TBottleChainLogAction).LogNames.Count-1 do begin
+              LogName := (Action as TBottleChainLogAction).LogNames[k];
+              LogName := StringReplaceEx(LogName, ReplaceHash);
+              LogNameList.Add(LogName);
+            end;
+          end;
+          if Action is TBottleChainOverrideGhostAction then
+          begin
+            Dat['TargetGhost'] := (Action as TBottleChainOverrideGhostAction).TargetGhost;
+          end;
+          if Action is TBottleChainQuitAction then
+            Application.Terminate;
+          if Action is TBottleChainSaveTextLogAction then
+            AppendTextLog(StringReplaceEx((Action as TBottleChainSaveTextLogAction).FileName, ReplaceHash),
+              Format('%s,%s,%s,%s', [Dat['Channel'], Dat['IfGhost'],
+                FormatDateTime('yy/mm/dd hh:nn:ss', Now), Dat['Script']]));
+          if Action is TBottleChainSaveXMLLogAction then
+            AppendXMLLog(StringReplaceEx((Action as TBottleChainSaveXMLLogAction).FileName, ReplaceHash),
+              Dat);
+          if Action is TBottleChainSurfaceReplaceAction then
+            Script := ReplaceSurface(Script, (Action as TBottleChainSurfaceReplaceAction).Params);
         end;
-        if Action is TBottleChainOverrideGhostAction then begin
-          Dat['TargetGhost'] := (Action as TBottleChainOverrideGhostAction).TargetGhost;
-        end;
-        if Action is TBottleChainQuitAction then Application.Terminate;
+        if BreakFlag then Break;
       end;
-      if BreakFlag then Break;
-    end;
 
-    if Dat['Script'] <> '' then begin
-      for i := 0 to LogNameList.Count-1 do
-        frmLog.AddCurrentScriptLog(LogNameList[i], Dat['Script'], Sender, Dat['MID'], Dat['IfGhost']);
-      if NoDispatch then begin
-        frmLog.SetBottleState(Dat['MID'], lsOpened);
-      end else begin
-        Ghost := Dat['TargetGhost']; // \83I\81[\83o\81[\83\89\83C\83h\82³\82ê\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82é
-        CueItem := TLogItem.Create(ltBottle, Dat['MID'], Dat['Channel'],
-          Script, Ghost, Now());
-        try
-          FBottleSstp.Push(CueItem);
-        except
-          CueItem.Free;
+      if Dat['Script'] <> '' then begin
+        for i := 0 to LogNameList.Count-1 do
+          frmLog.AddCurrentScriptLog(LogNameList[i], Dat['Script'], Sender, Dat['MID'], Dat['IfGhost']);
+        if NoDispatch then begin
+          frmLog.SetBottleState(Dat['MID'], lsOpened);
+        end else begin
+          Ghost := Dat['TargetGhost']; // \83I\81[\83o\81[\83\89\83C\83h\82³\82ê\82Ä\82¢\82é\89Â\94\\90«\82ª\82 \82é
+          CueItem := TLogItem.Create(ltBottle, Dat['MID'], Dat['Channel'],
+            Script, Ghost, Now());
+          try
+            FBottleSstp.Push(CueItem);
+          except
+            CueItem.Free;
+          end;
         end;
       end;
-    end;
 
-    if Dat['DialogMessage'] <> '' then begin
-      Beep;
-      frmMessageBox.ShowMessage(
-        DateTimeToStr(Now) + #13#10 +
-        'SSTP Bottle\83T\81[\83o\82©\82ç\82¨\92m\82ç\82¹'#13#10+Dat['DialogMessage']);
-      for i := 0 to LogNameList.Count-1 do
-        frmLog.AddCurrentSystemLog(LogNameList[i], Dat['DialogMessage']);
-    end;
+      if Dat['DialogMessage'] <> '' then begin
+        Beep;
+        frmMessageBox.ShowMessage(
+          DateTimeToStr(Now) + #13#10 +
+          'SSTP Bottle\83T\81[\83o\82©\82ç\82¨\92m\82ç\82¹'#13#10+Dat['DialogMessage']);
+        for i := 0 to LogNameList.Count-1 do
+          frmLog.AddCurrentSystemLog(LogNameList[i], Dat['DialogMessage']);
+      end;
 
-    //\89¹\82Ì\8dÄ\90
-    if (Sound <> '') then PlaySound(Sound);
+      //\89¹\82Ì\8dÄ\90
+      if (Sound <> '') then PlaySound(Sound);
+    finally
+      LogNameList.Free;
+    end;
   finally
-    LogNameList.Free;
-  end;
-end;
-
-function TfrmSender.SetHWndToFavoriteGhost(const Ghost: String): String;
-begin
-  //DirectSstp.TargetHWnd\82ð\81A\90\84\8f§\82·\82é\83S\81[\83X\83g\82É\90Ý\92è\82·\82é\81B
-  //\82È\82¢\8fê\8d\87\82Í\81A\82Æ\82è\82 \82¦\82¸\8eè\8bß\82È\83S\81[\83X\83g\82É\8cü\82¯\82Ä\91\97\90M\82Å\82«\82é\82æ\82¤\82É\82Í\82·\82é\81B
-  SakuraSeeker.BeginDetect; //\8dÅ\90V\82ÌFMO\8eæ\93¾
-  if SakuraSeeker.ProcessByName[Ghost] <> nil then begin
-    DirectSstp.TargetHWnd := SakuraSeeker.ProcessByName[Ghost].HWnd;
-    Result := Ghost;
-  end else if SakuraSeeker.Count > 0 then begin
-    DirectSstp.TargetHWnd := SakuraSeeker[0].HWnd;
-    Result := SakuraSeeker[0].Name;
-  end else begin
-    DirectSstp.TargetHwnd := 0;
-    Result := '';
+    Event.Free;
+    ReplaceHash.Free;
   end;
 end;
 
@@ -1681,9 +1848,10 @@ begin
   Orig := GetScriptText;
   RegExp.Subst('s/(\r\n)+$//kg', Orig);
 
-  if SsParser.InputString <> Orig then begin
-    //\90F\95ª\82¯\92x\89\84\82Ì\90Ý\92è\82É\82æ\82Á\82Ä\82Í\82±\82Ì2\82Â\82ª\90H\82¢\88á\82¤\89Â\94\\90«\82ª\82 \82é
-    SsParser.InputString := Orig
+  with SsParser do begin
+    LeaveEscape := true;
+    EscapeInvalidMeta := false;
+    InputString := Orig;
   end;
   for i := 0 to SsParser.Count-1 do begin
     if SsParser[i] <> '\e' then Text := Text + SsParser[i];
@@ -1721,9 +1889,9 @@ var PostStr: String;
 begin
   Connecting := true;
   PostStr := Command.Text;
-  PostStr := TIdURI.ParamsEncode(PostStr);
+  PostStr := ParamsEncode(PostStr);
   try
-    FHttp := THTTPDownloadThread.Create(BottleServer, Pref.CgiName, PostStr);
+    FHttp := THTTPDownloadThread.Create(Pref.BottleServer, Pref.CgiName, PostStr);
     if Pref.UseHttpProxy then begin
       FHttp.ProxyServer := Pref.ProxyAddress;
       FHttp.ProxyPort   := Pref.ProxyPort;
@@ -1740,28 +1908,6 @@ begin
   end;
 end;
 
-procedure TfrmSender.tabChannelDrawTab(Control: TCustomTabControl;
-  TabIndex: Integer; const Rect: TRect; Active: Boolean);
-var X, Y: integer;
-begin
-  with tabChannel.Canvas do begin
-    FillRect(Rect);
-    if Active then begin
-      Font.Color := clBlue;
-    end else begin
-      Font.Style := Font.Style - [fsBold];
-      Font.Color := clWindowText;
-    end;
-    X := (Rect.Left + Rect.Right) div 2;
-    X := X - TextWidth(tabChannel.Tabs[TabIndex]) div 2;
-    if tabChannel.TabPosition = tpTop then
-      Y := Rect.Top + 4
-    else
-      Y := Rect.Bottom - 15;
-    TextOut(X, Y, tabChannel.Tabs[TabIndex]);
-  end;
-end;
-
 procedure TfrmSender.actVoteMessageExecute(Sender: TObject);
 var Log: TLogItem;
 begin
@@ -1817,9 +1963,8 @@ procedure TfrmSender.PostSetChannel(Channels: TStrings);
 var PostStr: TStringList;
     i: integer;
 begin
-  PostStr := nil;
+  PostStr := TStringList.Create;
   try
-    PostStr := TStringList.Create;
     with PostStr do begin
       Add('Command: setChannels');
       Add('Agent: ' + VersionString);
@@ -1839,12 +1984,17 @@ procedure TfrmSender.mnLeaveThisChannelClick(Sender: TObject);
 var Ch: String;
     Chs: TStringList;
 begin
-  with tabChannel do Ch := Tabs[Tag];
-  Chs := nil;
+  // \8ew\92è\82µ\82½\83`\83\83\83\93\83l\83\8b\82©\82ç\94²\82¯\82é
+  with tabChannel do
+    Ch := Tabs[Tag]; // \94²\82¯\82½\82¢\83`\83\83\83\93\83l\83\8b\96¼
+  Chs := TStringList.Create;
+
+  // \8c»\8dÝ\8eQ\89Á\92\86\82Ì\83`\83\83\83\93\83l\83\8b\82©\82ç\81A\94²\82¯\82½\82¢\83`\83\83\83\93\83l\83\8b\82ð
+  // \8aO\82µ\82½\83\8a\83X\83g\82Å\81A\90V\82½\82É\83`\83\83\83\93\83l\83\8b\8eQ\89Á\83R\83}\83\93\83h\82ð\91\97\82é
   try
-    Chs := TStringList.Create;
     Chs.Assign(JoinChannels);
-    while Chs.IndexOf(Ch) >= 0 do Chs.Delete(Chs.IndexOf(Ch));
+    while Chs.IndexOf(Ch) >= 0 do
+      Chs.Delete(Chs.IndexOf(Ch));
     PostSetChannel(Chs);
   finally
     Chs.Free;
@@ -1856,11 +2006,6 @@ begin
   ShellExecute(Handle, 'open', PChar(Pref.VotePage), nil, nil, SW_SHOW);
 end;
 
-procedure TfrmSender.mnGotoGLogClick(Sender: TObject);
-begin
-  ShellExecute(Handle, 'open', PChar(Pref.GLogPage), nil, nil, SW_SHOW);
-end;
-
 procedure TfrmSender.tabChannelMouseMove(Sender: TObject;
   Shift: TShiftState; X, Y: Integer);
 var Index: integer;
@@ -1952,56 +2097,83 @@ end;
 
 procedure TfrmSender.cbxTargetGhostDrawItem(Control: TWinControl;
   Index: Integer; Rect: TRect; State: TOwnerDrawState);
+var AlignRight: boolean;
+    w: integer;
 begin
+  //\83S\81[\83X\83g\91I\91ð\83{\83b\83N\83X\82Ì\83I\81[\83i\81[\83h\83\8d\81[
   with cbxTargetGhost do begin
-    if Index > 0 then begin
+    AlignRight := false;
+    if Pref.HideGhosts and not FTargetGhostExpand and
+       (Index = Items.Count-1) and (Index > 0) then
+    begin
+      // \81u\82·\82×\82Ä\95\\8e¦\81v
+      Canvas.Font.Color := clWindowText;
+      Canvas.Font.Style := [];
+      AlignRight := true;
+    end else if (Index > 0) then
+    begin
+      // \83S\81[\83X\83g\96¼\82ð\91I\91ð
       if SakuraSeeker.ProcessByName[Items[Index]] = nil then
-        Canvas.Font.Color := clRed
+        Canvas.Font.Color := clRed // \91\8dÝ\82µ\82È\82¢\83S\81[\83X\83g
       else
-        Canvas.Font.Color := clBlue;
+        Canvas.Font.Color := clBlue; // \91\8dÝ\82·\82é\83S\81[\83X\83g
       Canvas.Font.Style := [fsBold];
-    end else begin
+    end else
+    begin
       Canvas.Font.Color := clWindowText;
       Canvas.Font.Style := [];
     end;
     if odSelected in State then
       Canvas.Font.Color := clHighlightText;
-    cbxTargetGhost.Canvas.TextRect(Rect, Rect.Left, Rect.Top,
-      cbxTargetGhost.Items[Index]);
+    // \95`\89æ
+    if AlignRight then
+    begin
+      w := Canvas.TextWidth(cbxTargetGhost.Items[Index]);
+      Canvas.TextRect(Rect, Rect.Right - w, Rect.Top,
+        cbxTargetGhost.Items[Index]);
+    end else
+      Canvas.TextRect(Rect, Rect.Left, Rect.Top,
+        cbxTargetGhost.Items[Index]);
   end;
 end;
 
 procedure TfrmSender.FormCloseQuery(Sender: TObject;
   var CanClose: Boolean);
 begin
-  if not Pref.ConfirmOnExit then Exit;
+  if (not Pref.ConfirmOnExit) or FEndSession then
+    Exit;
   if MessageDlg('SSTP Bottle Client\82ð\8fI\97¹\82µ\82Ü\82·', mtConfirmation,
-                mbOkCancel, 0) = mrCancel then CanClose := false;
+                mbOkCancel, 0) = mrCancel then
+    CanClose := false;
 end;
 
 procedure TfrmSender.UpdateIfGhostBox;
 var
   Selected: String;
   i: integer;
+  HiddenCount: integer;
 begin
   cbxTargetGhost.DropDownCount := Pref.GhostDropDownCount;
   Selected := cbxTargetGhost.Text;
+  HiddenCount := 0;
   with cbxTargetGhost do begin
     Items.BeginUpdate;
     Items.Clear;
-    Items.Add('(CH\90\84\8f§)');
+    Items.Add(ChannelDefault);
     for i := 0 to SakuraSeeker.Count-1 do begin
       // \94j\91¹FMO\91Î\8dô\81BHWND\82Ì\92f\95Ð\82ª\8ec\82Á\82Ä\82¢\82é\82ªName\82ª\8fÁ\82¦\82Ä\82¢\82é\8fê\8d\87\82ª\82 \82é
       if Length(SakuraSeeker[i].Name) = 0 then Continue;
-      if Pref.HideGhosts then
+      if Pref.HideGhosts and not FTargetGhostExpand then
         if Pref.VisibleGhostsList.IndexOf(SakuraSeeker[i].Name) < 0 then
+        begin
+          Inc(HiddenCount);
           Continue;
+        end;
       if cbxTargetGhost.Items.IndexOf(SakuraSeeker[i].Name) < 0 then
         cbxTargetGhost.Items.Add(SakuraSeeker[i].Name);
     end;
-    Items.EndUpdate;
     cbxTargetGhost.ItemIndex := 0;
-    if (Length(Selected) > 0) and (Selected <> '(CH\90\84\8f§)') then begin
+    if (Length(Selected) > 0) and (Selected <> ChannelDefault) then begin
       with cbxTargetGhost do begin
         for i := 1 to Items.Count-1 do begin
           if Items[i] = Selected then
@@ -2014,8 +2186,10 @@ begin
         end;
       end;
     end;
+    if Pref.HideGhosts and not FTargetGhostExpand then
+      Items.Add(Format('\82·\82×\82Ä(%d)...', [HiddenCount]));
+    Items.EndUpdate;
   end;
-
 end;
 
 procedure TfrmSender.HTTPFailure(Sender: TObject);
@@ -2023,7 +2197,8 @@ begin
   SysUtils.Beep;
   Beep;
   ShowHintLabel('SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½', WarningColor);
-  ShowMessage((Sender as THTTPDownloadThread).LastErrorMessage);
+  ShowMessage('SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82É\8e¸\94s\82µ\82Ü\82µ\82½'#13#10 +
+    (Sender as THTTPDownloadThread).LastErrorMessage);
   Connecting := false;
 end;
 
@@ -2034,7 +2209,12 @@ begin
   UpdateIfGhostBox;
   i := cbxTargetGhost.ItemIndex;
   Dec(i);
-  if i <= -1 then i := cbxTargetGhost.Items.Count-1;
+  if i <= -1 then
+  begin
+    i := cbxTargetGhost.Items.Count-1;
+    if Pref.HideGhosts and not FTargetGhostExpand then
+      i := i - 1;
+  end;
   cbxTargetGhost.ItemIndex := i;
   cbxTargetGhostChange(self);
 end;
@@ -2046,7 +2226,13 @@ begin
   UpdateIfGhostBox;
   i := cbxTargetGhost.ItemIndex;
   Inc(i);
-  if i > cbxTargetGhost.Items.Count-1 then i := 0;
+  if Pref.HideGhosts and not FTargetGhostExpand then
+  begin
+    if  i > cbxTargetGhost.Items.Count-2 then i := 0;
+  end else
+  begin
+    if  i > cbxTargetGhost.Items.Count-1 then i := 0;
+  end;
   cbxTargetGhost.ItemIndex := i;
   cbxTargetGhostChange(self);
 end;
@@ -2054,13 +2240,14 @@ end;
 procedure TfrmSender.actResetGhostExecute(Sender: TObject);
 begin
   cbxTargetGhost.ItemIndex := 0; // (CH\90\84\8f§)\82É\96ß\82·
+  FTargetGhostExpand := false;
   if Visible then memScript.SetFocus;
   cbxTargetGhostChange(self);
 end;
 
 procedure TfrmSender.timDisconnectCheckTimerTimer(Sender: TObject);
 begin
-  if (IdSlpp20.LastReadTimeInterval > BottleServerTimeOut) then begin
+  if (IdSlpp20.LastReadTimeInterval > Pref.ReconnectWait * 60000) then begin
     SysUtils.Beep;
     frmLog.AddCurrentSystemLog('SYSTEM', 'SSTP Bottle\83T\81[\83o\82Æ\82Ì\90Ú\91±\82ª\83^\83C\83\80\83A\83E\83g\82µ\82Ü\82µ\82½');
     if IdSlpp20.Connected then IdSlpp20.Disconnect;
@@ -2116,9 +2303,9 @@ begin
       with frmLogDownload do begin
         if IsRange then begin
           if CompareDate(DateLo, DateHi) = 0 then
-            Title := FormatDateTime('yy/mm/dd', DateLo)
+            Title := FormatDateTime('yy-mm-dd', DateLo)
           else
-            Title := FormatDateTime('yy/mm/dd', DateLo) + ' - ' + FormatdateTime('yy/mm/dd', DateHi);
+            Title := FormatDateTime('yy-mm-dd', DateLo) + ' - ' + FormatdateTime('yy-mm-dd', DateHi);
         end else begin
           Title := Format('\89ß\8b\8e%s', [TimeStr(RecentCount)]);
         end;
@@ -2175,7 +2362,7 @@ begin
   until Cond = '';
 end;
 
-procedure TfrmSender.BuildMenu(Root: TMenuItem; Event: TNotifyEvent; Simple: boolean);
+procedure TfrmSender.BuildMenu(Root: TMenuItem; Simple: boolean);
 var i, j, k, count: integer;
     ConstData: TScriptConst;
     Menu1, Menu2: TMenuItem;
@@ -2217,6 +2404,7 @@ begin
       Menu1.Hint    := ScriptConstList[i][j].Caption;
       Menu1.AutoHotkeys := maManual;
       Menu1.Tag := ScriptConstList[i][j].ID;
+      Menu1.OnClick := mnConstGroupClick;
 
       if not Simple then begin
         Root.Add(Menu1);
@@ -2233,10 +2421,11 @@ begin
         Menu2 := TMenuItem.Create(Root);
         Menu2.Caption := ConstData.Caption;
         Menu2.Hint := ConstData.ConstText;
-        if ConstData.ShortCut <> 0 then Menu2.Hint := Menu2.Hint
-          + ' (' + ShortCutToText(ConstData.ShortCut) + ')';
+        // if ConstData.ShortCut <> 0 then Menu2.Hint := Menu2.Hint
+        //   + ' (' + ShortCutToText(ConstData.ShortCut) + ')';
+        // \83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[\8eÀ\8c»\82Ì\82½\82ß\8fã\8dí\8f\9c
         Menu2.ShortCut := ConstData.ShortCut;
-        Menu2.OnClick := Event;
+        Menu2.OnClick := mnConstClick;
         Menu2.AutoHotkeys := maManual;
         Menu2.Tag := ConstData.ID;
         if (k mod 15 = 0) and (k > 0) then Menu2.Break := mbBarBreak;
@@ -2248,7 +2437,26 @@ end;
 
 procedure TfrmSender.cbxTargetGhostChange(Sender: TObject);
 begin
+  // \81u\82·\82×\82Ä\95\\8e¦\81v\82ð\91I\91ð\82³\82ê\82½\8fê\8d\87\82Í\83S\81[\83X\83g\91I\91ð\83{\83b\83N\83X\82ð\8dÄ\8d\\92z
+  if Pref.HideGhosts and not FTargetGhostExpand then
+  begin
+    with cbxTargetGhost do
+    begin
+      // \88ê\94Ô\89º\82Ì\83A\83C\83e\83\80\82ª\91I\91ð\82³\82ê\82½\82Æ\82«\81B
+      // \82½\82¾\82µItemIndex=0\82Ì\8fê\8d\87(\83S\81[\83X\83g\82ª\8bN\93®\82µ\82Ä\82¢\82È\82¢\8fê\8d\87)\82Í\97á\8aO
+      if (ItemIndex = Items.Count-1) and (ItemIndex > 0) then
+      begin
+        FTargetGhostExpand := true;
+        UpdateIfGhostBox;
+        ItemIndex := 0;
+        DroppedDown := true;
+      end;
+    end;
+  end;
+  // \92è\8c^\8bå\83\81\83j\83\85\81[\82ð\8dÄ\8d\\92z
   ConstructMenu(true);
+  // \83v\83\8c\83r\83\85\81[\82ª\82 \82é\8fê\8d\87\82Í\83v\83\8c\83r\83\85\81[
+  EditorPreview;
 end;
 
 procedure TfrmSender.PlaySound(const FileName: String);
@@ -2267,6 +2475,14 @@ end;
 
 procedure TfrmSender.actFMOExplorerExecute(Sender: TObject);
 begin
+  try
+    if not Assigned(frmFMOExplorer) then
+      Application.CreateForm(TfrmFMOExplorer, frmFMOExplorer);
+  except
+    on E: Exception do
+      ShowMessage('FMO\83G\83N\83X\83v\83\8d\81[\83\89\82ð\95\\8e¦\82Å\82«\82Ü\82¹\82ñ\81B'#13#10#13#10 +
+        E.Message);
+  end;
   frmFMOExplorer.Show;
 end;
 
@@ -2294,8 +2510,9 @@ end;
 
 procedure TfrmSender.actInsertCueExecute(Sender: TObject);
 var InsertItem: TLogItem;
-    i, errCount: integer;
+    i, errCount, Res: integer;
     Log: TBottleLogList;
+    ErrorMes: String; // \83X\83N\83\8a\83v\83g\82Ì\83G\83\89\81[\82Ì\93à\97e
 begin
   if FBottleSstp.CueCount > 0 then begin
     if MessageDlg(Format('\8c»\8dÝ\8dÄ\91\97\83L\83\85\81[\82É\93ü\82Á\82Ä\82¢\82é%d\8c\8f\82Ì\96¢\94z\91\97\83{\83g\83\8b\82ð\83N\83\8a\83A\82µ\82Ä\81A'+
@@ -2314,9 +2531,21 @@ begin
     if (Log[i] as TLogItem).LogType <> ltBottle then Continue;
     InsertItem := TLogItem.Create(Log[i] as TLogItem);
     try
-      InsertItem.Script := ScriptTransForSSTP(InsertItem.Script);
-      if InsertItem.Script = '' then begin
-        raise Exception.Create('Script syntax error');
+      InsertItem.Script := ScriptTransForSSTP(InsertItem.Script, ErrorMes);
+      if ErrorMes <> '' then
+      begin
+        Res := MessageDlg('\83X\83N\83\8a\83v\83g\82É\96â\91è\82ª\82 \82é\89Â\94\\90«\82ª\82 \82è\82Ü\82·\81B' +
+          '\8dÄ\90\82µ\82Ü\82·\82©?'#13#10 + ErrorMes, mtWarning,
+          mbYesNoCancel, 0);
+        if Res = mrNo then
+          raise Exception.Create('Script Syntax Error')
+        else if Res = mrCancel then
+        begin
+          InsertItem.Free;
+          FBottleSstp.Clear;
+          frmLog.AllBottleOpened;
+          Break;
+        end;
       end;
       if InsertItem.Ghost = '' then begin
         if ChannelList.Channel[InsertItem.Channel] <> nil then
@@ -2333,11 +2562,12 @@ begin
     ShowMessage(Format('%d\8c\8f\82Ì\83{\83g\83\8b\82É\96â\91è\82ª\82 \82Á\82½\82½\82ß\8dÄ\90\82Å\82«\82Ü\82¹\82ñ\81B', [errCount]));
   FBottleSSTP.OnResendCountChange := BottleSstpResendCountChange;
   BottleSstpResendCountChange(self);
+  frmLog.lvwLog.Invalidate;
 end;
 
-function TfrmSender.ScriptTransForSSTP(const Script: String): String;
+function TfrmSender.ScriptTransForSSTP(const Script: String;
+  out Error: String): String;
 var TransOpt: TScriptTransOptions;
-    Err: String;
 begin
   if Pref.NoTransURL then
     TransOpt := [toConvertURL, toNoChoice, toWaitScriptEnd]
@@ -2347,8 +2577,7 @@ begin
   if Pref.FixMessySurface then TransOpt := TransOpt + [toFixMessySurface];
   if Pref.HUTagTo01Tag then TransOpt := TransOpt + [toHUTagTo01Tag];
   Result := Script;
-  Err := DoTrans(Result, TransOpt);
-  if Length(Err) > 0 then Result := '';
+  Error := DoTrans(Result, TransOpt);
 end;
 
 procedure TfrmSender.FormResize(Sender: TObject);
@@ -2387,4 +2616,588 @@ begin
   memScript.SelectAll;
 end;
 
+procedure TfrmSender.actUndoExecute(Sender: TObject);
+begin
+  memScript.Undo;
+end;
+
+procedure TfrmSender.actRedoExecute(Sender: TObject);
+begin
+  memScript.Redo;
+end;
+
+function TfrmSender.IsSurfaceTag(const Script: String;
+  var ID: integer): boolean;
+begin
+  Result := false;
+  if SsParser.Match(Script, '\s%d') = 3 then
+  begin
+    Result := true;
+    ID := Ord(Script[3]) - Ord('0')
+  end
+  else if (Length(Script) > 0) and (SsParser.Match(Script, '\s[%D]') = Length(Script)) then
+  begin
+    Result := true;
+    ID := StrToIntDef(SsParser.GetParam(Script, 1), 0);
+  end;
+end;
+
+procedure TfrmSender.memScriptMouseMove(Sender: TObject;
+  Shift: TShiftState; X, Y: Integer);
+var id: integer;
+    token: String;
+begin
+  // \95Ò\8fW\83E\83B\83\93\83h\83E\82Å\83}\83E\83X\83|\83C\83\93\83g\82·\82é\82Æ\83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[
+  if not Pref.SurfacePreviewOnScriptPoint then
+    Exit;
+  token := memScript.TokenStringFromPos(Point(X, Y));
+  if IsSurfaceTag(token, id) then
+  begin
+    DoSurfacePreview(id, spEditor);
+  end else
+  begin
+    frmSurfacePreview.HideAway;
+  end;
+end;
+
+procedure TfrmSender.DoSurfacePreview(Surface: integer;
+  const Kind: TSurfacePreviewType);
+var Ghost: String;
+    Bmp: TBitmap;
+    CPos: TPoint;
+begin
+  if cbxTargetGhost.ItemIndex > 0 then
+    ghost := cbxTargetGhost.Text
+  else if FNowChannel <> '' then
+    ghost := ChannelList.Channel[FNowChannel].Ghost;
+
+  // \93ñ\8fd\95\\8e¦\82Ì\97}\90§
+  if (FVisiblePreviewGhost = Ghost) and (FVisiblePreviewSurface = Surface) and
+    not (frmSurfacePreview.IsHidden) then
+  begin
+    Exit;
+  end;
+
+  if ghost <> '' then
+  begin
+    Bmp := TBitmap.Create;
+    try
+      if Spps.TryGetImage(ghost, Surface, Bmp) then
+      begin
+        case Kind of
+          spHint:
+            CPos := GetSurfacePreviewPositionHint(Bmp.Width, Bmp.Height);
+          spEditor:
+            CPos := GetSurfacePreviewPositionScriptPoint(Bmp.Width, Bmp.Height);
+          else
+            CPos := Point(0, 0);
+        end;
+        frmSurfacePreview.ShowPreview(Bmp, CPos.X, CPos.Y);
+        FVisiblePreviewGhost := Ghost;
+        FVisiblePreviewSurface := Surface;
+      end else
+        frmSurfacePreview.HideAway;
+    finally
+      Bmp.Free;
+    end;
+  end;
+end;
+
+function TfrmSender.GetSurfacePreviewPositionHint(w, h: integer): TPoint;
+{var MousePoint: TPoint;
+    MenuRect: TRect;}
+begin
+  // \83T\81[\83t\83B\83X\83v\83\8c\83r\83\85\81[\83E\83B\83\93\83h\83E\82Ì\95\\8e¦\88Ê\92u\82ð\8c\88\92è\82·\82é
+  // \91\97\90M\83E\83B\83\93\83h\83E\82Ì\88Ê\92u\82É\82æ\82Á\82Ä\82Í\96­\82È\82Æ\82±\82ë\82É\83\81\83j\83\85\81[\82ª\95\\8e¦\82³\82ê\82Ä\82¢\82é\82Ì\82Å
+  // \88Ä\8aO\82â\82â\82±\82µ\82¢
+  {GetCursorPos(MousePoint);
+  OutputDebugString(PChar(IntToStr(FVisibleMenuItem.Count)));
+  //if GetMenuItemRect(Self.Handle, FVisibleMenuItem.Items[0].Handle, 1, MenuRect) then ;
+  Result := Point(MenuRect.Left-10, MenuRect.Top-10);}
+  Result := GetSurfacePreviewPositionScriptPoint(w, h);
+end;
+
+function TfrmSender.GetSurfacePreviewPositionScriptPoint(w, h: integer): TPoint;
+var MousePoint: TPoint;
+begin
+  GetCursorPos(MousePoint);
+  case Pref.SurfacePreviewOnScriptPointPosition of
+    spspMainWindowRight:
+      Result := Point(Self.Left + Self.Width, MousePoint.Y);
+  else
+      Result := Point(Self.Left - w, MousePoint.Y);
+  end;
+end;
+
+procedure TfrmSender.mnConstGroupClick(Sender: TObject);
+begin
+  if (Sender is TMenuItem) then
+    FVisibleMenuItem := Sender as TMenuItem;
+end;
+
+procedure TfrmSender.actRecallScriptBufferExecute(Sender: TObject);
+begin
+  if FScriptBuffer.Count = 0 then
+    Exit;
+  memScript.Lines.Assign(FScriptBuffer[0] as TStringList);
+  memScriptChange(Self);
+  ShowHintLabel('\83X\83N\83\8a\83v\83g\82ð\8cÄ\82Ñ\8fo\82µ\82Ü\82µ\82½');
+end;
+
+procedure TfrmSender.EditorPreview;
+var Ghost, Script: String;
+begin
+  if frmEditorTalkShow <> nil then
+    if frmEditorTalkShow.Visible then
+    begin
+      Script := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
+      Ghost := '';
+      if cbxTargetGhost.ItemIndex > 0 then
+        Ghost := cbxTargetGhost.Text
+      else if FNowChannel <> '' then
+        Ghost := ChannelList.Channel[FNowChannel].Ghost;
+      frmEditorTalkShow.TalkShowFrame.View(Script, Ghost);
+    end;
+end;
+
+procedure TfrmSender.actEditorPreviewExecute(Sender: TObject);
+begin
+  if frmEditorTalkShow <> nil then
+    frmEditorTalkShow.Show
+  else
+  begin
+    Application.CreateForm(TfrmEditorTalkShow, frmEditorTalkShow);
+    frmEditorTalkShow.Show;
+  end;
+  EditorPreview;
+end;
+
+// \83v\83\89\83O\83C\83\93\83\8a\83Z\83b\83g
+procedure TfrmSender.actResetPluginsExecute(Sender: TObject);
+begin
+  Spps.ClearImagePool;
+  Spps.LoadFromDirectory(FSppDir);
+end;
+
+procedure TfrmSender.IdSLPP20Connect(Sender: TObject);
+begin
+  ShowHintLabel('SSTP Bottle\83T\81[\83o\82ª\8c©\82Â\82©\82è\82Ü\82µ\82½');
+end;
+
+// \83X\83N\83\8a\83v\83g\92\86\82Ì\83^\83O\82ð\92u\8a·\82·\82é
+// \83T\83C\83Y\89Â\95Ï\82Ì\94z\97ñ\83p\83\89\83\81\81[\83^\94Å
+function TfrmSender.TagReplace(Script: String; Before,
+  After: array of String): String;
+var BeforeList, AfterList: TStringList;
+    i: integer;
+begin
+  BeforeList := TStringList.Create;
+  AfterList  := TStringList.Create;
+  try
+    for i := Low(Before) to High(Before) do
+      BeforeList.Add(Before[i]);
+    for i := Low(After) to High(After) do
+      AfterList.Add(After[i]);
+    Result := TagReplace(Script, BeforeList, AfterList);
+  finally
+    BeforeList.Free;
+    AfterList.Free;
+  end;
+end;
+
+// \83X\83N\83\8a\83v\83g\92\86\82Ì\83^\83O\82ð\92u\8a·\82·\82é
+// StringReplace\82Æ\88á\82Á\82Ä\90³\8am\82É\83^\83O\82É\83}\83b\83`\82µ\81A
+// \82Ü\82½\83p\83^\81[\83\93\82ð\95¡\90\94\8ew\92è\82Å\82«\82é(\92u\8a·\8cã\82Ì\8c\8b\89Ê\82ª\82Ü\82½\92u\8a·\82³\82ê\82½\82è\82µ\82È\82¢)
+function TfrmSender.TagReplace(Script: String; Before,
+  After: TStrings): String;
+var i, j: integer;
+    Flag, OldLeaveEscape, OldEscapeInvalidMeta: boolean;
+    OldStr: String;
+begin
+  Result := '';
+  with SsParser do
+  begin
+    OldStr := InputString;
+    OldLeaveEscape := LeaveEscape;
+    OldEscapeInvalidMeta := EscapeInvalidMeta;
+    LeaveEscape := true;
+    EscapeInvalidMeta := false;
+    InputString := Script;
+  end;
+  for i := 0 to SsParser.Count-1 do
+  begin
+    Flag := false;
+    for j := 0 to Before.Count-1 do
+    begin
+      if (SsParser.MarkUpType[i] = mtTag) and (SsParser[i] = Before[j]) then
+      begin
+        Flag := true;
+        Result := Result + After[j];
+      end;
+    end;
+    if not Flag then
+      Result := Result + SsParser[i];
+  end;
+  with SsParser do
+  begin
+    LeaveEscape := OldLeaveEscape;
+    EscapeInvalidMeta := OldEscapeInvalidMeta;
+    InputString := OldStr;
+  end;
+end;
+
+// WndProc\82ð\83I\81[\83o\81[\83\89\83C\83h\82µ\82Ä\81AFWM_TaskBarCraeted\82É
+// \91Î\89\9e\82·\82é
+procedure TfrmSender.WndProc(var Message: TMessage);
+begin
+  if (Message.Msg = FWM_TaskBarCreated) and (FWM_TaskBarCreated <> 0) then
+  begin
+    TaskTray.Registered := false; // TTaskTray\82É\83^\83X\83N\83g\83\8c\83C\82ª\8fÁ\82¦\82½\82±\82Æ\82ð
+                                  // \8bC\82Ã\82©\82¹\82é
+    ChangeTaskIcon;
+  end
+  else
+    inherited;
+end;
+
+// \92u\8a·
+procedure TfrmSender.actReplaceExecute(Sender: TObject);
+var
+  Form: TfrmStrReplaceDialog;
+  Lines: String;
+  Pair: TReplacePairRec;
+  Options: TReplaceFlags;
+begin
+  Application.CreateForm(TfrmStrReplaceDialog, Form);
+  try
+    if Form.Execute then
+    begin
+      Pair := Form.PairRec;
+      with Pair do
+      begin
+        Lines := memScript.Lines.Text;
+        Options := [rfReplaceAll];
+        if IgnoreCase then
+          Options := Options + [rfIgnoreCase];
+        if UseRegExp then
+          Lines := StringReplaceRegExp(Lines, BeforeStr, AfterStr, Options)
+        else
+          Lines := StringReplace(Lines, BeforeStr, AfterStr, Options);
+      end;
+      if Lines <> memScript.Lines.Text then
+      begin
+        memScript.SelectAll;
+        memScript.SelText := Lines;
+      end;
+    end;
+  finally
+    Form.Release;
+  end;
+end;
+
+procedure TfrmSender.actSendToEditorExecute(Sender: TObject);
+var Log: TLogItem;
+begin
+  if frmLog.lvwLog.Selected = nil then Exit;
+  Log := frmLog.SelectedBottleLog[frmLog.lvwLog.Selected.Index] as TLogItem;
+  if Log = nil then Exit;
+  CopyFromLogToEditor(Log);
+end;
+
+procedure TfrmSender.actSendToLogWindowExecute(Sender: TObject);
+var Ghost, Script: String;
+begin
+  YenETrans;
+  Script := StringReplace(GetScriptText, #13#10, '', [rfReplaceAll]);
+  if cbxTargetGhost.ItemIndex > 0 then
+    Ghost := cbxTargetGhost.Text
+  else
+    Ghost := '';
+  frmLog.AddCurrentScriptLog('\83N\83\8a\83b\83v', Script, ClipChannel, '', Ghost);
+  ClearEditor;
+end;
+
+procedure TfrmSender.memScriptDragOver(Sender, Source: TObject; X,
+  Y: Integer; State: TDragState; var Accept: Boolean);
+begin
+  // \83\8d\83O\83E\83B\83\93\83h\83E\82©\82ç\82Ì\83\8d\83O\83A\83C\83e\83\80\82Ì\92¼\90ÚD&D\82ð\8b\96\89Â\82·\82é
+  if Source is TBottleLogDragObject then
+    Accept := (Source as TBottleLogDragObject).LogItem.LogType = ltBottle
+end;
+
+procedure TfrmSender.memScriptDragDrop(Sender, Source: TObject; X,
+  Y: Integer);
+var Src: TBottleLogDragObject;
+    Log: TLogItem;
+begin
+  // \83\8d\83O\83E\83B\83\93\83h\83E\82©\82ç\83\8d\83O\83A\83C\83e\83\80\82ðD&D\82µ\82Ä\82­\82é
+  if not (Source is TBottleLogDragObject) then
+    Exit;
+  if (Source as TBottleLogDragObject).LogItem.LogType <> ltBottle then
+    Exit;
+  Src := Source as TBottleLogDragObject;
+  Log := Src.LogItem;
+  CopyFromLogToEditor(Log);
+end;
+
+procedure TfrmSender.CopyFromLogToEditor(Log: TLogItem);
+begin
+  if Log.LogType <> ltBottle then Exit;
+  frmSender.actClear.Execute; // \8c»\8dÝ\82Ì\83X\83N\83\8a\83v\83g\82ð\83N\83\8a\83b\83v\82·\82é(\90Ý\92è\82É\82æ\82Á\82Ä)
+  memScript.Lines.Clear;
+  memScript.Lines.Add(Log.Script);
+  if Log.Ghost <> '' then
+  begin
+    // \83S\81[\83X\83g\96¼\82ð\83{\83b\83N\83X\82É\93ü\82ê\82é
+    // \96³\97\9d\96î\97\9d\83S\81[\83X\83g\96¼\82ð\92Ç\89Á\82µ\82Ä\82©\82ç\8dÄ\8d\\92z\82·\82é\82±\82Æ\82Å
+    // \96³\97\9d\96î\97\9d\83S\81[\83X\83g\96¼\82ª\83{\83b\83N\83X\82É\93ü\82é
+    cbxTargetGhost.Items.Add(Log.Ghost);
+    cbxTargetGhost.ItemIndex := cbxTargetGhost.Items.Count-1;
+    UpdateIfGhostBox;
+    cbxTargetGhost.Invalidate;
+  end else
+    cbxTargetGhost.ItemIndex := 0; // 'CH\90\84\8f§'\82É\82·\82é
+  memScript.SetFocus;
+end;
+
+procedure TfrmSender.actDeleteLogItemExecute(Sender: TObject);
+begin
+  // \83\8d\83O\83E\83B\83\93\83h\83E\82Ì\8cÂ\95Ê\83\8d\83O\82ð\8dí\8f\9c\82·\82é
+  if frmLog.SelectedBottleLog = nil then
+    Exit;
+  if frmLog.lvwLog.Selected = nil then
+    Exit;
+  frmLog.SelectedBottleLog.Delete(frmLog.lvwLog.Selected.Index);
+  frmLog.UpdateWindow;
+  frmLog.lvwLogChange(Self, nil, ctState);
+end;
+
+procedure TfrmSender.ClearEditor;
+var TmpScript: String;
+    Position: Integer;
+    DoSaveBuffer: boolean;
+    SavedScript: TStringList;
+begin
+  // \83X\83N\83\8a\83v\83g\82Ì\83N\83\8a\83A
+  // \82Ü\82¸\81A\83X\83N\83\8a\83v\83g\83N\83\8a\83A\83o\83b\83t\83@\82É\8c»\8dÝ\82Ì\83X\83N\83\8a\83v\83g\82ð\95Û\91\82·\82é
+  DoSaveBuffer := false;
+  if FScriptBuffer.Count = 0 then
+    DoSaveBuffer := true
+  else if (FScriptBuffer[0] as TStringList).Text <> GetScriptText then
+    DoSaveBuffer := true;
+  if (GetScriptText = Pref.DefaultScript) or (GetScriptText = '') then
+    DoSaveBuffer := false;
+  if DoSaveBuffer then
+  begin
+    SavedScript := TStringList.Create;
+    SavedScript.Text := GetScriptText;
+    FScriptBuffer.Insert(0, SavedScript);
+  end;
+  if FScriptBuffer.Count >= 4 then
+    FScriptBuffer.Delete(FScriptBuffer.Count-1);
+  actRecallScriptBuffer.Enabled := FScriptBuffer.Count > 0;
+
+  TmpScript := Pref.DefaultScript;
+  Position := Pos('|', TmpScript);
+  if Position < 1 then Position := 1;
+  TmpScript := StringReplace(TmpScript, '|', '', []);
+  memScript.Lines.Text := TmpScript;
+  Sendmessage(memScript.Handle, WM_VSCROLL, SB_LINEUP, 0);
+  memScript.SelStart := Position-1;
+
+  if Visible then memScript.SetFocus;
+  FScriptModified := false;
+  memScriptChange(self);
+end;
+
+procedure TfrmSender.AppendTextLog(const FileName, Line: String);
+var
+  F: TextFile;
+begin
+  //\91\97\90M\83\8d\83O\95Û\91
+  try
+    ForceDirectories(ExtractFileDir(FileName));
+    AssignFile(F, FileName);
+    if FileExists(FileName) then
+      Append(F)
+    else
+      Rewrite(F);
+    WriteLn(F, Line);
+    Flush(F);
+    CloseFile(F);
+  except
+    on E: Exception do
+      frmLog.AddCurrentSystemLog('SYSTEM', '\83e\83L\83X\83g\83\8d\83O\95Û\91\82É\8e¸\94s\81F'+E.Message);
+  end;
+end;
+
+procedure TfrmSender.AppendXMLLog(const FileName: String; Args: THeadValue);
+var
+  F: TFileStream;
+  Buf: String;
+  P: integer;
+  Impl: TDomImplementation;
+  Parser: TXmlToDomParser;
+  DOM: TdomDocument;
+begin
+  try // Long try block to handle all kinds of exception in this method
+    if not FileExists(FileName) then
+    begin
+      // Create empty XML log file
+      Impl := TDomImplementation.create(nil);
+      try
+        ForceDirectories(ExtractFileDir(FileName));
+        Parser := TXmlToDomParser.create(nil);
+        Parser.DOMImpl := Impl;
+        try
+          try
+            DOM := Parser.fileToDom(ExtractFilePath(Application.ExeName)+'xbtl.dat');
+            with DOM do
+            begin
+              documentElement.setAttribute('saved',
+                FormatDateTime('yy/mm/dd hh:nn:ss', Now));
+              documentElement.setAttribute('generator', VersionString);
+              documentElement.setAttribute('version', '1.0');
+            end;
+            // \82±\82ê\82Í\96¾\8e¦\93I\82ÉFree\82µ\82È\82­\82Ä\82æ\82¢
+            F := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
+            try
+              DOM.writeCodeAsShiftJIS(F);
+            finally
+              F.Free;
+            end;
+          except
+            frmLog.AddCurrentSystemLog('SYSTEM', 'XML\83\8d\83O\95Û\91\82É\8e¸\94s\82µ\82Ü\82µ\82½');
+          end;
+        finally
+          Parser.DOMImpl.freeDocument(DOM);
+          Parser.Free;
+        end;
+      finally;
+        Impl.Free;
+      end;
+    end;
+    F := TFileStream.Create(FileName, fmOpenReadWrite or fmShareExclusive);
+    try
+      P := -11;
+      SetLength(Buf, 12);
+      while P > -100 do
+      begin
+        F.Seek(P, soFromEnd);
+        F.Read(Buf[1], 12);
+        if Buf = '</bottlelog>' then
+          Break;
+        Dec(P);
+      end;
+      if P = -100 then
+        raise Exception.Create(FileName + ' is not a valid XML bottle log file')
+      else
+      begin
+        F.Seek(P, soFromEnd);
+        Buf := Format('<message mid="%s">', [Args['MID']]);
+        Buf := Buf + Format('<date>%s</date>', [FormatDateTime('yy/mm/dd hh:nn:ss', Now)]);
+        Buf := Buf + Format('<channel>%s</channel>', [XMLEntity(Args['Channel'])]);
+        Buf := Buf + Format('<script>%s</script>', [XMLEntity(Args['Script'])]);
+        Buf := Buf + '<votes>0</votes><agrees>0</agrees>';
+        Buf := Buf + Format('<ghost>%s</ghost>', [XMLEntity(Args['IfGhost'])]);
+        Buf := Buf + '</message>';
+        Buf := Buf + '</bottlelog>';
+        F.Write(Buf[1], Length(Buf));
+      end;
+    finally
+      F.Free;
+    end;
+  except
+    on E: Exception do
+      frmLog.AddCurrentSystemLog('SYSTEM', 'XML\83\8d\83O\95Û\91\82É\8e¸\94s\82µ\82Ü\82µ\82½:'+E.Message);
+  end;
+end;
+
+procedure TfrmSender.memScriptSelectionChange(Sender: TObject;
+  Selected: Boolean);
+var
+  SelText: String;
+begin
+  SelText := memScript.SelText;
+  if SelText <> '' then
+  begin
+    StatusBar.Panels[PanelBytes].Text := Format('(%d\83o\83C\83g)', [Length(SelText)]);
+  end else
+  begin
+    memScriptChange(Self);
+  end; 
+end;
+
+function TfrmSender.ReplaceSurface(Script: String;
+  Params: TCollection): String;
+var
+  Flag, OldLeaveEscape, OldEscapeInvalidMeta: boolean;
+  OldStr, Tag: String;
+  i, j, k, Cur: integer;
+  Item: TSurfaceReplaceItem;
+  Before: TSurfaceReplaceBeforeItem;
+begin
+  Result := '';
+  Cur := 0;
+  with SsParser do
+  begin
+    OldStr := InputString;
+    OldLeaveEscape := LeaveEscape;
+    OldEscapeInvalidMeta := EscapeInvalidMeta;
+    LeaveEscape := true;
+    EscapeInvalidMeta := false;
+    InputString := Script;
+  end;
+  for i := 0 to SsParser.Count-1 do
+  begin
+    if SsParser.MarkUpType[i] <> mtTag then
+    begin
+      Result := Result + SsParser.Str[i];
+      Continue;
+    end;
+    Tag := SsParser.Str[i];
+    if SsParser.Match(Tag, '\s%d') = 2 then
+      Cur := Ord(Tag[3]) - Ord('0')
+    else if SsParser.Match(Tag, '\s[%D]') > 0 then
+      Cur := StrToInt(SsParser.GetParam(Tag, 1))
+    else
+    begin
+      Result := Result + Tag;
+      Continue;
+    end;
+    Flag := false;
+    for j := 0 to Params.Count-1 do
+    begin
+      Item := Params.Items[j] as TSurfaceReplaceItem;
+      for k := 0 to Item.Before.Count-1 do
+      begin
+        Before := Item.Before.Items[k] as TSurfaceReplaceBeforeItem;
+        if (Cur >= Before.FromNo) and (Cur <= Before.ToNo) then
+        begin
+          Flag := true;
+          Result := Result + Format('\s[%d]', [Item.After]);
+          Break;
+        end;
+      end;
+      if Flag then
+        Break;
+    end;
+    if not Flag then
+      Result := Result + Tag;
+  end;
+  with SsParser do
+  begin
+    LeaveEscape := OldLeaveEscape;
+    EscapeInvalidMeta := OldEscapeInvalidMeta;
+    InputString := OldStr;
+  end;
+end;
+
+procedure TfrmSender.WMQueryEndSession(var msg: TWMQueryEndSession);
+begin
+  // Windows\82ª\8fI\97¹\82µ\82æ\82¤\82Æ\82µ\82Ä\82¢\82é\82Ì\82ð\8a´\92m\82·\82é
+  FEndSession := true;
+  inherited;
+end;
+
 end.