MessageMarketBuy(市场购买请求)
事实基准:
harvestcraft-1.3.14-GTNH.jar(MC 1.7.10 / Forge 10.13.4.1614)CFR 0.152 反编译源码。
基本信息
| 属性 | 值 | 来源 |
|---|---|---|
| 类 | com.pam.harvestcraft.MessageMarketBuy |
MessageMarketBuy.java:18 |
| 实现接口 | IMessage、IMessageHandler<MessageMarketBuy, IMessage>(自处理) |
MessageMarketBuy.java:19-20 |
| 判别器 | 1 |
PacketHandler.java:18 |
| 通道 | harvestcraft(SimpleNetworkWrapper) |
PacketHandler.java:14 |
| 目标侧 | Side.SERVER |
PacketHandler.java:18 |
| 方向 | 客户端 → 服务端 | 三个发送点均在 GuiMarket |
| 发送点 | GuiMarket.java:80、:82、:85 |
PacketHandler.INSTANCE.sendToServer(...) |
承载的数据
toBytes / fromBytes(MessageMarketBuy.java:38-52)共 4 × int + 1 × boolean,共 17 字节:
| 顺序 | 字段 | 类型 | 字节 | 语义 |
|---|---|---|---|---|
| 1 | itemNum |
int |
4 | 要购买的物品序号(MarketItems.getData(itemNum) 的下标) |
| 2 | x |
int |
4 | 市场方块 X |
| 3 | y |
int |
4 | 市场方块 Y |
| 4 | z |
int |
4 | 市场方块 Z |
| 5 | shouldClear |
boolean |
1 | true = 整个清空货币槽;false = 只扣 price,余额留槽 |
坐标由 GuiMarket 从 tileEntityMarket 取出:field_145851_c / field_145848_d / field_145849_e(GuiMarket.java:80)。这三个是 SRG 名,本机无 MCP 映射表 → 未解析;语义佐证:它们被当作 int 直接塞进包的 (int x, int y, int z) 构造参数,并在服务端被 func_147438_o(x, y, z)(取方块实体)使用——即 TileEntity 的 xCoord / yCoord / zCoord。
客户端侧的三个发送分支
GuiMarket 在购买按钮(guibutton.field_146127_k == 2)被点击时,先校验槽内物品确实是 data.getCurrency()(GuiMarket.java:76,比较 func_77973_b() 与 func_77960_j()——两者均为 SRG 名 → 未解析,语义佐证:分别与 ItemStack 的物品/损伤比较配套使用),再按下表三分支:
| # | 条件 | shouldClear |
含义 | 来源 |
|---|---|---|---|---|
| 1 | 槽内数量 == price |
true |
正好付清 → 整个清空 | GuiMarket.java:79-80 |
| 2 | 槽内数量 > price 且 > 1 |
false |
多付 → 只扣 price,余量退回槽内 |
GuiMarket.java:81-82 |
| 3 | 槽内数量 == 0 且 price == 1 |
true |
价格为 1 时的"空手购买" | GuiMarket.java:84-85 |
field_77994_a是ItemStack的堆叠数(SRG 名 → 未解析;语义佐证:与int price做==/>比较,且分支 3 用== 0判空槽)。- 三个分支是并列的
if,不是互斥的else if:field_146127_k == 2之后先判分支 1/2,再判分支 3。当price == 1且槽内数量正好为 1 时,分支 1 命中;若槽内为 0,则只有分支 3 命中。分支 1 与 2 互斥(==与>),不会重复发包。
服务端处理(onMessage,MessageMarketBuy.java:54-70)
-
取
EntityPlayerMP:ctx.getServerHandler().field_147369_b(SRG → 未解析,语义佐证同 MessageMarketBrowse)。 -
按坐标取方块实体,仅当
instanceof TileEntityMarket时继续(MessageMarketBuy.java:56-57)。 -
MarketItems.getData(message.itemNum)取得MarketData,data.getPrice()(MarketData.java:27)取价格(MessageMarketBuy.java:59-60)。 -
分支(
MessageMarketBuy.java:61-65):shouldClear == true→tileEntityMarket.clearInventory()(TileEntityMarket.java:73)清空整个交易槽;shouldClear == false→tileEntityMarket.takeEmeraldFromSlot(price)(TileEntityMarket.java:66)只扣price。
-
无论哪种分支,随后无条件把商品丢到玩家脚下(
MessageMarketBuy.java:66-67):- 构造
EntityItem(world, playerX, playerY + 1.0, playerZ, data.getItem().func_77946_l()) player.field_70170_p.func_72838_d(entityItem)放入世界。field_70165_t/field_70163_u/field_70161_v= 玩家 x/y/z(SRG → 未解析;语义佐证:被EntityItem构造器当作 double 坐标使用,且 Y 额外+ 1.0)。func_72838_d=World.spawnEntityInWorld(SRG → 未解析;语义佐证:唯一入参是刚构造的Entity)。func_77946_l()=ItemStack.copy()(SRG → 未解析;语义佐证:返回ItemStack供EntityItem构造器使用,避免与槽内共享同一ItemStack实例)。
- 构造
-
return null(MessageMarketBuy.java:69):无应答包。
源码缺陷 / 观察
- 服务端不校验
shouldClear与实际槽内容的匹配:clearInventory()与takeEmeraldFromSlot(price)都在instanceof之后直接执行,客户端传的shouldClear决定行为。这是典型的"客户端权威"信任模型(1.7.10 时代的常见做法),服务端不复核货币种类与数量。仅陈述源码事实,不作为漏洞结论——本条目未在游戏中验证。 - 三个发送分支中,分支 3(
price == 1且槽空)会走clearInventory(),与分支 1 走同一条服务端路径。 - 购买不消耗
itemNum以外的任何购买者身份信息;itemNum越界时MarketItems.getData的行为取决于该方法实现,本条目未展开。
相关条目
- PacketHandler —— 判别器
1的注册处 - MessageMarketBrowse · MessageMarketClosed
- AbstractPacket · 旧版空壳包
- 兄弟维度:世界生成 · 配置 · 兼容层