Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c4df1df
Initial plan
Copilot Jan 31, 2026
2722779
Add all missing methods and fields to 仙剑98DX9.cs
Copilot Jan 31, 2026
241de7a
Fix string formatting in import success message
othercat Jan 31, 2026
d0bef5d
Fix missing methods: Replace TS2HMMSSMS and add save/load functionality
Copilot Jan 31, 2026
6859c11
Fix DX9 version detection and add 仙剑98 prefix to version display
Copilot Jan 31, 2026
e85c66e
Add grace period for PAL.DLL to modify window title for DX9 detection
Copilot Jan 31, 2026
a4ab451
Fix window handle transition issue - handle sdf window and empty titles
Copilot Jan 31, 2026
9d29ddf
Update version to 3.35, improve DX9 patch support
othercat Jan 31, 2026
43e4195
Update references to new patch in about and DX9 files
othercat Feb 1, 2026
410799b
Implement 4 new features: monster counter, real-time battle stats, si…
Copilot Feb 1, 2026
ef692ba
Release v3.35.1 with 2025 patch support and fixes
othercat Feb 1, 2026
196fc50
Fix main timer hour display clipping with large fonts
Copilot Feb 1, 2026
2782591
Allow DX9 to load PAL98 plugins for bottom-left display compatibility
Copilot Feb 1, 2026
3118a13
Fix LoadPlugins compilation errors - make virtual and fix namespace
Copilot Feb 1, 2026
3d36dd3
Fix milliseconds display wrapping by increasing allocated width
Copilot Feb 1, 2026
c07e0ae
Fix 简版 layout to prevent overlapping and text clipping
Copilot Feb 1, 2026
d5ee099
Bump version to 3.35.2 and update DX9 references
othercat Feb 1, 2026
cb04bbf
Fix proportional scaling of three columns in checkpoint items section
Copilot Feb 1, 2026
1da7a7e
Implement adaptive width for name column based on text content
Copilot Feb 1, 2026
26dee48
Make all three columns adaptive based on text content
Copilot Feb 1, 2026
beca119
Fix missing BestStr and CurrentStr properties by using TimeSpan forma…
Copilot Feb 1, 2026
c12a0f8
Fix top section stretching and center-align main timer
Copilot Feb 1, 2026
acc0cdb
Fix top section layout - correctly handle two-row structure
Copilot Feb 1, 2026
adb4dee
Adjust checkpoint column text alignments: left, center, right
Copilot Feb 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions KeyChanger/MainForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions KeyChanger/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public MainForm()

ShowKCEnable();
ShowBCEEnable();
niMain.ShowBalloonTip(1000, "改建器", "已启动,双击图标打开按键展示,右键设置", ToolTipIcon.Info);
niMain.ShowBalloonTip(1000, "改键器", "已启动,双击图标打开按键展示,右键设置", ToolTipIcon.Info);
try
{
initForPaint();
Expand Down Expand Up @@ -400,7 +400,7 @@ private void MainForm_Shown(object sender, EventArgs e)

private void Exit()
{
niMain.ShowBalloonTip(1000, "改建器", "已退出", ToolTipIcon.Warning);
niMain.ShowBalloonTip(1000, "改键器", "已退出", ToolTipIcon.Warning);
_keyboardHook.UninstallHook();
niMain.Dispose();
Environment.Exit(0);
Expand Down
5 changes: 4 additions & 1 deletion Pal98Timer/AboutForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ private Dictionary<string, string[]> InitCreaterInfo()
r.Add("齐大", new string[3] { "齐小伙", "http://www.palspeed.com/tj.html?p11", "1" });
r.Add("麻烦", new string[3] { "麻烦(鼻祖)", "http://www.palspeed.com/tj.html?p12", "1" });
r.Add("wjjjj12", new string[3] { "wjjjj12", "http://www.palspeed.com/tj.html?p13", "1" });
r.Add("othercat", new string[3] { "othercat", "https://space.bilibili.com/349512", "1" });
r.Add("!官网", new string[3] { "www.palspeed.com", "http://www.palspeed.com", "2" });
r.Add("!各位玩家", new string[3] { "各位玩家", "http://www.palspeed.com/tj.html?n1", "2" });
r.Add("!github", new string[3] { "https://github.com/ihouou/PalTimer", "https://github.com/ihouou/PalTimer", "2" });
Expand All @@ -47,7 +48,9 @@ private HText InitData(Dictionary<string, string[]> c)
.Title("视觉")
.Text("界面:").Link(c["Houou"]).Space().Text("图标:").Link(c["兰"]).Space().Link(c["狐狸"])
.Title("内核贡献")
.Text("仙剑98柔情:").Link(c["麻烦"]).Space().Link(c["!各位玩家"])
.Text("仙剑98原版 2.0/3.0补丁:").Link(c["麻烦"]).Space().Link(c["!各位玩家"])
.Line()
.Text("仙剑98原版 2025新补丁:").Link(c["othercat"])
.Line()
.Text("仙剑98 Steam:").Link(c["寒泠"])
.Line()
Expand Down
142 changes: 121 additions & 21 deletions Pal98Timer/GEX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -857,8 +857,8 @@ public bool Draw(Graphics g, bool isForceDrawAll, GBoard bb, Rectangle rcName, R
g.FillRectangles(bb.CPItemBG, new Rectangle[] { rcName, rcBest });
}

GEX.DrawText(g, _name, bb.CPNameFont, bb.CPNameFill, bb.CPNameBorder, rcName, GLayout.sfCC);
GEX.DrawText(g, TS2HHMMSS(_best), bb.CPBestFont, bb.CPBestFill, bb.CPBestBorder, rcBest, GLayout.sfFC);
GEX.DrawText(g, _name, bb.CPNameFont, bb.CPNameFill, bb.CPNameBorder, rcName, GLayout.sfNC);
GEX.DrawText(g, TS2HHMMSS(_best), bb.CPBestFont, bb.CPBestFill, bb.CPBestBorder, rcBest, GLayout.sfCC);
if (!isForceDrawAll)
{
ur?.Invoke(rcName);
Expand Down Expand Up @@ -888,21 +888,21 @@ public bool Draw(Graphics g, bool isForceDrawAll, GBoard bb, Rectangle rcName, R
//快
fill = bb.CPGoodFill;
border = bb.CPGoodBorder;
GEX.DrawText(g, _cha.Hours == 0 ? ("-" + TS2MSS(_cha)) : ("-" + TS2HMMSS(_cha)), bb.CPChaFont, fill, border, rcCha, GLayout.sfFC);
GEX.DrawText(g, _cha.Hours == 0 ? ("-" + TS2MSS(_cha)) : ("-" + TS2HMMSS(_cha)), bb.CPChaFont, fill, border, rcCha, GLayout.sfCC);
}
else if (_cha.Ticks > 10000000)
{
//慢
fill = bb.CPBadFill;
border = bb.CPBadBorder;
GEX.DrawText(g, _cha.Hours == 0 ? ("+" + TS2MSS(_cha)) : ("+" + TS2HMMSS(_cha)), bb.CPChaFont, fill, border, rcCha, GLayout.sfFC);
GEX.DrawText(g, _cha.Hours == 0 ? ("+" + TS2MSS(_cha)) : ("+" + TS2HMMSS(_cha)), bb.CPChaFont, fill, border, rcCha, GLayout.sfCC);
}
else
{
//同
fill = bb.CPSameFill;
border = bb.CPSameBorder;
GEX.DrawText(g, "0:00", bb.CPChaFont, fill, border, rcCha, GLayout.sfFC);
GEX.DrawText(g, "0:00", bb.CPChaFont, fill, border, rcCha, GLayout.sfCC);
}

GEX.DrawText(g, TS2HHMMSSFF(_cur), bb.CPCurFont, fill, border, rcCur, GLayout.sfFC);
Expand Down Expand Up @@ -1035,20 +1035,40 @@ private void ModifyRect(ref Rectangle rect, int x, int y, int w, int h)
}
private void BuildRects()
{
ModifyRect(ref rcTitle, 5, 5, Width - 100, 26);
ModifyRect(ref rcGameVersion, 5, 31, GEX.GDIMulti(Width, 0.7F), 26);
ModifyRect(ref rcVersion, rcGameVersion.X + rcGameVersion.Width, rcGameVersion.Y, Width - 2 * rcGameVersion.X - rcGameVersion.Width, rcGameVersion.Height);
// Top section: Two rows that stretch to fill full width
// Row 1: Title (full width)
ModifyRect(ref rcTitle, 5, 5, Width - 10, 26);
// Row 2: GameVersion (left 70%) and Version (right 30%)
ModifyRect(ref rcGameVersion, 5, 31, GEX.GDIMulti(Width, 0.70F), 26);
ModifyRect(ref rcVersion, rcGameVersion.X + rcGameVersion.Width, rcGameVersion.Y, Width - rcGameVersion.X - rcGameVersion.Width - 5, rcGameVersion.Height);
ModifyRect(ref rcBL, 5, Height - 26, GEX.GDIMulti(Width, 0.4F), 26);
ModifyRect(ref rcBR, rcBL.X + rcBL.Width, rcBL.Y, Width - 2 * rcBL.X - rcBL.Width, rcBL.Height);

ModifyRect(ref rcMoreInfo, 5, Height - 100, Width - 10, 26);
ModifyRect(ref rcMainTimer, 20, Height - 150, Width - 65, 50);
ModifyRect(ref rcMainTimerMS, rcMainTimer.X + rcMainTimer.Width, rcMainTimer.Y, Width - 25 - rcMainTimer.Width, rcMainTimer.Height);
ModifyRect(ref rcIsC, -1, rcMainTimer.Y + 5, 20, rcMainTimer.Height - 10);
// For small windows (简版: 270x200), adjust layout to prevent overlapping
if (Height <= 250)
{
// Compact layout for 简版
ModifyRect(ref rcMoreInfo, 5, Height - 75, Width - 10, 20);
ModifyRect(ref rcMainTimer, 0, Height - 120, Width - 70, 40);
ModifyRect(ref rcMainTimerMS, rcMainTimer.X + rcMainTimer.Width, rcMainTimer.Y, Width - 10 - rcMainTimer.Width, rcMainTimer.Height);
ModifyRect(ref rcIsC, -1, rcMainTimer.Y + 5, 20, rcMainTimer.Height - 10);

ModifyRect(ref rcSubTimer, 10, Height - 140, GEX.GDIMulti(Width, 0.35F), 20);
ModifyRect(ref rcOutTimer, rcSubTimer.X + rcSubTimer.Width, rcSubTimer.Y, Width - 2 * rcSubTimer.X - rcSubTimer.Width, rcSubTimer.Height);
ModifyRect(ref rcWillClear, 10, Height - 160, Width - 20, 20);
}
else
{
// Standard layout for normal windows
ModifyRect(ref rcMoreInfo, 5, Height - 100, Width - 10, 26);
ModifyRect(ref rcMainTimer, 0, Height - 150, Width - 85, 50);
ModifyRect(ref rcMainTimerMS, rcMainTimer.X + rcMainTimer.Width, rcMainTimer.Y, Width - 25 - rcMainTimer.Width, rcMainTimer.Height);
ModifyRect(ref rcIsC, -1, rcMainTimer.Y + 5, 20, rcMainTimer.Height - 10);

ModifyRect(ref rcSubTimer, 10, Height - 170, GEX.GDIMulti(Width, 0.35F), 26);
ModifyRect(ref rcOutTimer, rcSubTimer.X + rcSubTimer.Width, rcSubTimer.Y, Width - 2 * rcSubTimer.X - rcSubTimer.Width, rcSubTimer.Height);
ModifyRect(ref rcWillClear, 10, Height - 200, Width - 20, 26);
ModifyRect(ref rcSubTimer, 10, Height - 170, GEX.GDIMulti(Width, 0.35F), 26);
ModifyRect(ref rcOutTimer, rcSubTimer.X + rcSubTimer.Width, rcSubTimer.Y, Width - 2 * rcSubTimer.X - rcSubTimer.Width, rcSubTimer.Height);
ModifyRect(ref rcWillClear, 10, Height - 200, Width - 20, 26);
}

ModifyRect(ref rcDots, 0, 60, Width, 30);

Expand All @@ -1071,10 +1091,90 @@ private void BuildRects_Item(bool showScroll)
ModifyRect(ref rcItems, 0, 95, Width, Height - 200 - 95);
ModifyRect(ref rcItemScroll, 0, 0, 0, 0);
}
ModifyRect(ref rcIName, rcItems.X, 0, rcItems.Width - 170, bb.ItemHeight);
ModifyRect(ref rcIBest, rcItems.X + rcItems.Width - 170, 0, 60, bb.ItemHalfHeight);
ModifyRect(ref rcICha, rcIBest.X, 0, rcIBest.Width, bb.ItemHeight - bb.ItemHalfHeight);
ModifyRect(ref rcICur, rcItems.X + rcItems.Width - 110, 0, 110, bb.ItemHeight);

// Calculate adaptive widths for all three columns based on actual text content
int nameWidth, bestWidth, curWidth;
CalculateOptimalColumnWidths(rcItems.Width, out nameWidth, out bestWidth, out curWidth);

ModifyRect(ref rcIName, rcItems.X, 0, nameWidth, bb.ItemHeight);
ModifyRect(ref rcIBest, rcItems.X + nameWidth, 0, bestWidth, bb.ItemHalfHeight);
ModifyRect(ref rcICha, rcIBest.X, bb.ItemHalfHeight, rcIBest.Width, bb.ItemHeight - bb.ItemHalfHeight);
ModifyRect(ref rcICur, rcItems.X + nameWidth + bestWidth, 0, curWidth, bb.ItemHeight);
}

private void CalculateOptimalColumnWidths(int totalWidth, out int nameWidth, out int bestWidth, out int curWidth)
{
// If no graphics context or no items, use reasonable defaults
if (CG == null || itemList.Count == 0)
{
nameWidth = GEX.GDIMulti(totalWidth, 0.50F);
bestWidth = GEX.GDIMulti(totalWidth, 0.12F);
curWidth = totalWidth - nameWidth - bestWidth;
return;
}

// Measure the widest text in each column
float maxNameWidth = 0;
float maxBestWidth = 0;
float maxCurWidth = 0;

foreach (GItem item in itemList)
{
// Measure name column
SizeF nameSize = CG.MeasureString(item.Name, bb.CPNameFont);
if (nameSize.Width > maxNameWidth)
{
maxNameWidth = nameSize.Width;
}

// Measure best time column (format: "00:23:42" or similar)
string bestText = TS2HHMMSS(item.Best);
SizeF bestSize = CG.MeasureString(bestText, bb.CPBestFont);
if (bestSize.Width > maxBestWidth)
{
maxBestWidth = bestSize.Width;
}

// Measure current time column (format: "00:23:42.67" with milliseconds)
string curText = TS2HHMMSSFF(item.Cur);
SizeF curSize = CG.MeasureString(curText, bb.CPCurFont);
if (curSize.Width > maxCurWidth)
{
maxCurWidth = curSize.Width;
}
}

// Add padding: 20% for names (relatively static), 30% for times (can change)
int calculatedName = (int)(maxNameWidth * 1.2F);
int calculatedBest = (int)(maxBestWidth * 1.3F);
int calculatedCur = (int)(maxCurWidth * 1.3F);

// Apply per-column constraints
// Name: min 25%, max 55%
int minName = GEX.GDIMulti(totalWidth, 0.25F);
int maxName = GEX.GDIMulti(totalWidth, 0.55F);
nameWidth = Math.Max(minName, Math.Min(maxName, calculatedName));

// Best: min 10%, max 25%
int minBest = GEX.GDIMulti(totalWidth, 0.10F);
int maxBest = GEX.GDIMulti(totalWidth, 0.25F);
bestWidth = Math.Max(minBest, Math.Min(maxBest, calculatedBest));

// Current: min 20%, max 50%
int minCur = GEX.GDIMulti(totalWidth, 0.20F);
int maxCur = GEX.GDIMulti(totalWidth, 0.50F);
curWidth = Math.Max(minCur, Math.Min(maxCur, calculatedCur));

// Check if total exceeds available width and scale proportionally if needed
int total = nameWidth + bestWidth + curWidth;
if (total > totalWidth)
{
// Scale all columns proportionally to fit
float scale = (float)totalWidth / (float)total;
nameWidth = (int)(nameWidth * scale);
bestWidth = (int)(bestWidth * scale);
curWidth = totalWidth - nameWidth - bestWidth; // Ensure exact fit
}
}
public GBoardChanger CurrentEdit = null;
public delegate void delOnEditCurrentChanged(GBoardChanger gbc);
Expand Down Expand Up @@ -2331,11 +2431,11 @@ private void DrawMainTimer(Graphics g, delUpdateRect ur = null)
}
if (isInCheck)
{
GEX.DrawText(g, "*" + TS2HHMMSS(MainTimer), bb.MainTimerFont, bb.MainTimerFill, bb.MainTimerBorder, rcMainTimer, GLayout.sfFC);
GEX.DrawText(g, "*" + TS2HHMMSS(MainTimer), bb.MainTimerFont, bb.MainTimerFill, bb.MainTimerBorder, rcMainTimer, GLayout.sfCC);
}
else
{
GEX.DrawText(g, TS2HHMMSS(MainTimer), bb.MainTimerFont, bb.MainTimerFill, bb.MainTimerBorder, rcMainTimer, GLayout.sfFC);
GEX.DrawText(g, TS2HHMMSS(MainTimer), bb.MainTimerFont, bb.MainTimerFill, bb.MainTimerBorder, rcMainTimer, GLayout.sfCC);
}
if (!isSizeChanged && !isBGChanged && !isBBChanged)
{
Expand Down
6 changes: 4 additions & 2 deletions Pal98Timer/GForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Pal98Timer
{
public partial class GForm : NoneBoardFormEx
{
public const string CurrentVersion = "3.34.1";
public const string CurrentVersion = "3.35.2";
public const string bgpath = @"bg.png";
private TimerCore core;
private bool IsAutoLuck = false;
Expand Down Expand Up @@ -130,7 +130,7 @@ public GForm():base(true)
}
catch (Exception ex)
{
LoadCore(new 仙剑98柔情(this));
LoadCore(new 仙剑98DX9(this));
}

rr.SetVersion(CurrentVersion);
Expand Down Expand Up @@ -534,9 +534,11 @@ public void OnKeyPress(KeyboardLib.HookStruct hookStruct, out bool handle)
if (KeyChangerDel.IsEnable())
{
KeyChangerDel.Disable();
KeyChangerDel.Close(); // 功能4:关闭KEYCHANGER.EXE进程
}
else
{
KeyChangerDel.Open(); // 功能4:重新打开KEYCHANGER.EXE
KeyChangerDel.Enable();
}
ShowKCEnable();
Expand Down
2 changes: 1 addition & 1 deletion Pal98Timer/KeyChangerDel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class KeyChangerDel
private const string tar = "KeyChanger";
private static int call(int act,int data=0)
{
int hwnd = FindWindow(null, "改建器");
int hwnd = FindWindow(null, "改键器");
if (hwnd != 0)
{
return SendMessage(hwnd, TIMERCALL, (IntPtr)act, (IntPtr)data).ToInt32();
Expand Down
29 changes: 22 additions & 7 deletions Pal98Timer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Windows.Forms;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace Pal98Timer
{
Expand All @@ -16,13 +17,27 @@ static class Program
[STAThread]
static void Main()
{
ClearTmpBG();
UpdateBestFiles();
KeyChangerDel.Open();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new GForm());
//Application.Run(new BestEditForm("CUSTOM"));
// 功能3:单实例检查
bool createdNew;
using (Mutex mutex = new Mutex(true, "Pal98Timer_SingleInstance_Mutex", out createdNew))
{
if (!createdNew)
{
MessageBox.Show("自动计时器已经在运行中!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}

ClearTmpBG();
UpdateBestFiles();
KeyChangerDel.Open();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new GForm());
//Application.Run(new BestEditForm("CUSTOM"));

// 保持mutex直到程序退出
GC.KeepAlive(mutex);
}
}
static void ClearTmpBG()
{
Expand Down
4 changes: 2 additions & 2 deletions Pal98Timer/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
// 方法是按如下所示使用“*”:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.34")]
[assembly: AssemblyFileVersion("3.34")]
[assembly: AssemblyVersion("3.35.2")]
[assembly: AssemblyFileVersion("3.35.2")]
2 changes: 1 addition & 1 deletion Pal98Timer/TimerCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ public string GetRStr()
/// <summary>
/// 加载此内核能用的所有插件
/// </summary>
public void LoadPlugins()
public virtual void LoadPlugins()
{
string pluginPath = TimerPluginPackageInfo.GetPluginDir();
if (!Directory.Exists(pluginPath)) return;
Expand Down
Loading