exe4j 打包出来的 exe 报找不到 jvm
用 exe4j 把一个 JavaFX 桌面程序打成 exe,自带 JRE 一起发出去,结果双击 exe 直接弹「找不到 jvm」之类的错。机器上明明装了 Java,自带的 JRE 目录也都在,就是起不来。
折腾一圈发现是 exe4j 找 JRE 的路径没配对。
exe4j 打出来的 exe 本质是个启动器,它自己不包含 JVM,运行的时候要先在机器上找一个可用的 JRE 再拉起真正的程序。它找 JRE 是按一个「搜索顺序」来的,默认会先去找系统注册的 Java、JAVA_HOME 之类,找不到才往下走。问题就出在这:开发机上系统 Java 是高版本的,能蒙混过去;到了用户那台干净机器上,啥系统 Java 都没有,它又没被告诉「去我自带的那个 ./jre 里找」,自然就报找不到。
修法是在 exe4j 的配置里显式给一个搜索路径,优先指向打包进去的那个 JRE 目录。exe4j 的配置是份 xml,对应的是 <searchSequence> 这一段:
<searchSequence> <directory location="./jre ...
GPR 背景去除:擦掉公共水平条带
整张 GPR 剖面上常有一层「每道都长一样」的横向条带,像照片蒙了灰雾。擦掉它,局部目标才显出来。
从哪来有些成分在全测线每一道都几乎相同:
直达波:发射天线直接窜到接收天线,每道开头都有;
地表或天线面的反射、耦合响应;
大范围的均匀层状反射。
它们在每个位置出现的时间和强度都差不多,画出来就是水平横条,盖住局部异常。
平均道就是背景对每个时间样点,把所有道的值求平均,得到一条「平均道」。水平背景在每道都一样,在平均里相干保留;局部目标位置相位不同,互相抵消。
从每道减去这条平均道,背景没了,局部目标活下来。
统一公式:
$$D_{out}[t,x]=D[t,x]-\alpha\cdot\hat B[t,x],\qquad \alpha\in[0,1]$$
$\alpha=1$ 全减;$\alpha<1$ 部分减,适合既看点目标又想看连续界面的情况。
背景估计的几种方法
全局 mean/median:所有道一起求均值或中位数。
滑窗 mean/median:只取当前道附近窗口内的道求平均,适合背景沿测线缓慢变化。
SVD:取数据矩阵前 ...
雷达剖面背景去除的几种做法
探地雷达的剖面图(B-scan)上,经常有一些横向几乎不变的条带或底色——直达波、天线环回响、系统本底。它们跟具体的地下反射没关系,却往往占了大头,把真正想看的弱反射压在下面看不清。背景去除就是想办法把这些共模背景估出来再减掉。
先跟 dewow 划清界限:dewow 沿时间轴(每道内部)去道自己的低频漂移;背景去除沿空间轴(道与道之间)去横向的共模背景。两者方向正交。统一写成一个公式就是 D − α·B̂——估一个背景 B̂,乘个强度系数 α 再减掉,alpha 默认 1.0 就是全减。
难点不在「减」,在「背景 B̂ 怎么估才估得准」。试下来几种路子各有擅长的场景。
mean / median:沿线平均,最朴素最直接的是沿空间方向(跨道)求平均当背景。直达波这种几乎每道都一样的东西,一平均就冒出来了,减掉很干净。把均值换成中位数(median)就是 median 法,好处是抗异常——少数几道的强反射不会把整体背景带偏。
这一类对「横向基本恒定」的背景效果好。但雷达数据里的背景不总是规规矩矩水平的,有时候是斜的(剖面沿线有倾角)、有时候是周期性振铃(ringing),mea ...
离线软件怎么防用户改系统时间绕过到期
前面写过一版防时间回拨,思路是本地存一个「上次见过的时间」,下次启动发现现在比它还早,就判定时间被调过。那招够用但比较朴素,只能挡住随手调表的人。后来又琢磨了一圈,离线环境里想认真防「改系统时间续命」,其实有几条不同路数的思路,单独哪一条都有破法,得叠着用。
先说个大前提:完全离线、又不能联网对时的环境下,这是软件防破解里的经典难题,没有银弹。下面这些都是抬门槛,不是绝对防住。
别看「现在几点」,看「过去留的痕」第一类思路是文件时间戳审计。软件运行的时候,往系统各处(日志、临时目录、配置文件深处、注册表角落)偷偷写一些文件。每次启动去读这些文件的最后修改时间,只要发现当前系统时间比某个历史文件的修改时间还早——说明时间被往回调了,直接锁死。逻辑是:正常情况下,现在不可能比过去还早。
这招成本最低,但文件能被删,删干净了就重置,所以只能当一道初筛。
时间能倒退,运行时长不能第二类是单向计数器,我觉得是更靠谱的方向。墙上的时钟能调,但「软件实际跑了多久」这件事,用户没法自然地把它调回去。
具体两种做法。一种是限制使用次数:许可证带一个最大次数,每次启动在本地加密文件里扣一次,用完就锁。另一 ...
dewow 这一步去的是低频漂移
处理探地雷达(GPR)数据,拿来第一步基本都先做 dewow。刚接触的时候光看名字不知道它在去什么,后来弄明白了,记一下。
先说两个词。雷达每次发射接收,得到的是一条沿时间方向的采样序列,叫一道(A-scan);把很多道沿测线方向排开,就成了二维的剖面图(B-scan)。dewow 处理的是「一道」内部的事。
一道信号本来该围绕 0,实际常常整体漂理论上,去掉直达波以后,一道雷达信号应该围绕 0 这条基线上下抖动——有反射的地方抖一下,没反射的地方贴着 0。实际拿到的数据不是这样,整条信号会慢慢往上拱一段、又往下沉一段,整体不贴着 0,像是在一个缓慢起伏的包络上叠加了高频反射。这个慢漂就叫 wow。
wow 这个词是早期音响那边的行话,本来指唱片转速不稳导致的声音低频飘忽,借过来形容信号的低频基线漂移。它的来源一般是天线跟地面的耦合、仪器本底、线缆感应这些,跟真正的地下反射没关系,但它占着基线、带着整道信号跑偏。
为什么要先去掉它——基线不归零,后面一堆处理都受拖累。算能量、判反射强度的时候,慢漂会垫一层虚假的底;显示的时候,整道压在直流偏置上,弱反射更看不清。
沿时间方向算个滑动平均 ...
keygen 这套软件许可服务是怎么设计的
之前研究了一下 keygen(GitHub 上 keygen-sh/keygen-api,一个开源的软件许可服务),想搞清楚工业级的软件授权到底是怎么组织的。看完最大的感受是,它早就不是那种「写个算法、算出一串序列号、客户端拿同样算法校验」的老路子了——那种算法只要被逆出来就全军覆没。keygen 把授权拆成了一套类似图模型的东西在服务端动态管。
整个体系围绕三个东西转:策略(Policy)、许可证(License)、设备(Machine)。策略是规则模板,比如「能激活 3 台机器、有效期一年、每 30 天必须联网一次」;许可证是卖给用户的实体,绑在某个策略上;设备是许可证真正消耗的额度,每激活一台就建一个 Machine 节点、扣一个名额。这套拆法把「卖了多少份」「每份能绑几台」「谁在用」分得很干净,加规则改策略就行,不用动许可证本身。
离线验签靠非对称加密我觉得这是它最核心的一手。光靠联网请求校验有个致命问题:破解者本地起一个假服务器(mock),把你客户端的请求全拦下来返回「合法」,校验就形同虚设。keygen 的做法是服务端用非对称加密(Ed25519)的私钥给许可证 ...
Claude Code 突然版本不兼容
Claude Code显示与64位版本windows不兼容打开 Claude Code,直接弹个框,提示“与 64 位版本 Windows 不兼容”。
折腾半天发现跟系统、跟兼容性都没关系,是 Claude 的启动程序自己损坏了。先去这个目录看一眼:
C:\Users\你的用户名\AppData\Roaming\npm\node_modules\@anthropic-ai\claude-code\bin
如果里面的启动程序只有 1kb,直接重装大概率没用(个人实测)。正确做法是用 winget 重新拉一个下来:
winget install Anthropic.ClaudeCode
这步需要魔法。
装完用 where claude 找到刚下载的那个可执行文件,复制粘贴覆盖掉 bin 目录里损坏的那个,重开一个命令窗口,基本就好了。(我好了)
构建插件里扫码付款自动发 licence
一个 Java 工具做成 Gradle/Maven 插件分发,想收费,就希望用户在命令行或者构建的时候直接扫码付款、licence 自动发到本机、构建接着跑,全程不用人工干预。整个链路梳理下来其实不复杂,但有几个点不注意会出问题。
整体流程是这样:用户跑构建的时候插件发现没 licence,调服务端创建一个订单(订单绑死本机指纹),服务端调微信 Native 下单拿到一个 code_url;插件把这个 code_url 在控制台打成 ASCII 二维码;用户扫码付钱;微信异步回调服务端,或者服务端主动查单兜底;服务端确认收款后凭订单里绑的指纹签发 licence、落库;插件轮询把 licence 拉下来,写盘前用内嵌公钥重新验签一遍、再比对一次本机指纹,确认无误才写进 ~/.myapp/app.lic。
几个关键点分开说。
订单绑指纹,金额服务端定
创建订单的时候,客户端把本机算的指纹一起传上去,服务端把指纹和订单绑死。金额由服务端的价格表决定,客户端传什么都改不了。这样防的是改价——客户端是用户能改的,金额判断不能放客户端。
实付金额必须等于订单金额
微信回调或者查单拿到” ...
浮点数比较踩的坑
做参数一致性检查的时候遇到个怪事:明明几个文件的相关参数是一样的,检查却报”不一致”。打印出来一看,一个是 8.0,另一个是 7.9999995。
原因是这些参数从文件头里按 float(单精度)读出来,再赋给 double 做比较。float 的有效位大概只有 7 位十进制,一个理论上等于 8 的值,存进 float 再转 double,很可能就变成 7.9999995… 这种近似值。原来的检查直接用 != 精确比较,把这种近似相等的值判成了不等。
// 原来:精确比较,float 的近似噪声会被误判if (p.timeWindow != first.timeWindow) { /* 报不一致 */ }// 改成容差比较private fun approxEqual(a: Double, b: Double): Boolean = abs(a - b) < 1e-3if (!approxEqual(p.timeWindow, first.timeWindow)) { /* 报不一致 */ }
浮点数不能直接用 == / ...
多个 JavaFX 属性怎么联动同步
场景很常见:界面上好几个图表或者控件,它们的某个属性要保持联动——比如多个子图的 X 轴范围要一致、缩放倍数要同步,一个改了其他几个跟着变。
最朴素的写法是给每个属性挂监听器,监听器里去 set 别的属性。但很快就会踩坑:set 别的属性又会触发它们的监听器,绕回来形成回环;浮点的值 set 来 set 去,精度抖动让属性在 20 和 19.9999 之间来回跳;还有属性对象被替换的时候,旧的监听器没摘干净,内存泄漏。
后来干脆写了个通用的同步器,把这些坑一次性解决。核心不长:
class PropertySyncManager<T> { var epsilon: Double = 1e-9 private val propertyMap = mutableMapOf<Property<T>, ChangeListener<T>>() private var isSyncing = false fun register(property: Property<T>) { ...
