跳到正文
烈焰甘蔗Wiki

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)

  1. 取 EntityPlayerMP:ctx.getServerHandler().field_147369_b(SRG → 未解析,语义佐证同 MessageMarketBrowse)。

  2. 按坐标取方块实体,仅当 instanceof TileEntityMarket 时继续(MessageMarketBuy.java:56-57)。

  3. MarketItems.getData(message.itemNum) 取得 MarketData,data.getPrice()(MarketData.java:27)取价格(MessageMarketBuy.java:59-60)。

  4. 分支(MessageMarketBuy.java:61-65):

    • shouldClear == true → tileEntityMarket.clearInventory()(TileEntityMarket.java:73)清空整个交易槽;
    • shouldClear == false → tileEntityMarket.takeEmeraldFromSlot(price)(TileEntityMarket.java:66)只扣 price。
  5. 无论哪种分支,随后无条件把商品丢到玩家脚下(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 实例)。
  6. return null(MessageMarketBuy.java:69):无应答包。

源码缺陷 / 观察

  • 服务端不校验 shouldClear 与实际槽内容的匹配:clearInventory() 与 takeEmeraldFromSlot(price) 都在 instanceof 之后直接执行,客户端传的 shouldClear 决定行为。这是典型的"客户端权威"信任模型(1.7.10 时代的常见做法),服务端不复核货币种类与数量。仅陈述源码事实,不作为漏洞结论——本条目未在游戏中验证。
  • 三个发送分支中,分支 3(price == 1 且槽空)会走 clearInventory(),与分支 1 走同一条服务端路径。
  • 购买不消耗 itemNum 以外的任何购买者身份信息;itemNum 越界时 MarketItems.getData 的行为取决于该方法实现,本条目未展开。

相关条目

浏览全部模组