gbsplay-0.0.102/0000755000175100017510000000000015105657176012736 5ustar runnerrunnergbsplay-0.0.102/gbcpu.c0000644000175100017510000013477515105657176014223 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2003-2021 (C) by Tobias Diedrich * Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include #include #include #include "gbcpu.h" #if DEBUG == 1 static const char regnames[12] = "BCDEHLFASPPC"; static const char *regnamech16[6] = { "BC", "DE", "HL", "AF", "SP", "PC" }; static const char *conds[4] = { "NZ", "Z", "NC", "C" }; #endif struct opinfo; typedef void (*ex_fn)(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi); struct opinfo { #if DEBUG == 1 || defined(S_SPLINT_S) const char* const name; #endif const ex_fn fn; #if DEBUG == 1 const char cycles_1; const char cycles_2; #endif }; static uint32_t none_get(void *priv, uint32_t addr) { UNUSED(priv); UNUSED(addr); return 0xff; } static void none_put(void *priv, uint32_t addr, uint8_t val) { UNUSED(priv); UNUSED(addr); UNUSED(val); } void gbcpu_init_struct(struct gbcpu* const gbcpu) { for (uint16_t i = 0; i < GBCPU_LOOKUP_SIZE; i++) { gbcpu->getlookup[i].get = &none_get; gbcpu->putlookup[i].put = &none_put; } } static inline uint32_t mem_get(struct gbcpu* const gbcpu, uint32_t addr) { struct get_entry *e = &gbcpu->getlookup[(addr >> 8) & 0xff]; gbcpu->cycles += 4; return e->get(e->priv, addr); } static inline void mem_put(struct gbcpu* const gbcpu, uint32_t addr, uint32_t val) { struct put_entry *e = &gbcpu->putlookup[(addr >> 8) & 0xff]; gbcpu->cycles += 4; e->put(e->priv, addr, val); } uint8_t gbcpu_mem_get(struct gbcpu* const gbcpu, uint16_t addr) { return mem_get(gbcpu, addr); } void gbcpu_mem_put(struct gbcpu* const gbcpu, uint16_t addr, uint8_t val) { mem_put(gbcpu, addr, val); } static void push(struct gbcpu* const gbcpu, uint32_t val) { uint32_t sp = REGS16_R(gbcpu->regs, SP) - 2; REGS16_W(gbcpu->regs, SP, sp); mem_put(gbcpu, sp, val & 0xff); mem_put(gbcpu, sp+1, val >> 8); } static uint32_t pop(struct gbcpu* const gbcpu) { uint32_t res; uint32_t sp = REGS16_R(gbcpu->regs, SP); res = mem_get(gbcpu, sp); res += mem_get(gbcpu, sp+1) << 8; REGS16_W(gbcpu->regs, SP, sp + 2); return res; } static uint32_t get_imm8(struct gbcpu* const gbcpu) { uint32_t pc = REGS16_R(gbcpu->regs, PC); uint32_t res; REGS16_W(gbcpu->regs, PC, pc + 1); res = mem_get(gbcpu, pc); DPRINTF("%02x", res); return res; } static uint32_t get_imm16(struct gbcpu* const gbcpu) { uint32_t pc = REGS16_R(gbcpu->regs, PC); uint32_t res; REGS16_W(gbcpu->regs, PC, pc + 2); res = mem_get(gbcpu, pc) + (mem_get(gbcpu, pc+1) << 8); DPRINTF("%02x%02x", res & 0xFF, res >> 8); return res; } static inline void print_reg(long i) { if (i == 6) DPRINTF("[HL]"); /* indirect memory access by [HL] */ else DPRINTF("%c", regnames[i]); } static uint32_t get_reg(struct gbcpu* const gbcpu, long i) { if (i == 6) /* indirect memory access by [HL] */ return mem_get(gbcpu, REGS16_R(gbcpu->regs, HL)); return REGS8_R(gbcpu->regs, i); } static void put_reg(struct gbcpu* const gbcpu, long i, uint32_t val) { if (i == 6) /* indirect memory access by [HL] */ mem_put(gbcpu, REGS16_R(gbcpu->regs, HL), val); else REGS8_W(gbcpu->regs, i, val); } static void op_unknown(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(oi); fprintf(stderr, "\n\nUnknown opcode %02x.\n", (unsigned char)op); gbcpu->stopped = 1; } static void op_set(struct gbcpu* const gbcpu, uint32_t op) { long reg = op & 7; unsigned long bit = (op >> 3) & 7; DPRINTF("\tSET %ld, ", bit); print_reg(reg); put_reg(gbcpu, reg, get_reg(gbcpu, reg) | (1 << bit)); } static void op_res(struct gbcpu* const gbcpu, uint32_t op) { long reg = op & 7; unsigned long bit = (op >> 3) & 7; DPRINTF("\tRES %ld, ", bit); print_reg(reg); put_reg(gbcpu, reg, get_reg(gbcpu, reg) & ~(1 << bit)); } static void op_bit(struct gbcpu* const gbcpu, uint32_t op) { long reg = op & 7; unsigned long bit = (op >> 3) & 7; DPRINTF("\tBIT %ld, ", bit); print_reg(reg); gbcpu->regs.rn.f &= ~NF; gbcpu->regs.rn.f |= HF | ZF; gbcpu->regs.rn.f ^= ((get_reg(gbcpu, reg) << 8) >> (bit+1)) & ZF; } static void op_rl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { /* C <- rrrrrrrr <- * | | * -------------- */ long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res << 1; res |= (gbcpu->regs.rn.f & CF) >> 4; gbcpu->regs.rn.f = (val >> 7) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_rla(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { /* C <- aaaaaaaa <- * | | * -------------- */ uint8_t res; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); res = gbcpu->regs.rn.a; res = res << 1; res |= (gbcpu->regs.rn.f & CF) >> 4; gbcpu->regs.rn.f = (gbcpu->regs.rn.a >> 7) << 4; gbcpu->regs.rn.a = res; } static void op_rlc(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { /* C <- rrrrrrrr <- * | | * ----------- */ long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res << 1; res |= val >> 7; gbcpu->regs.rn.f = (val >> 7) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_rlca(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { /* C <- aaaaaaaa <- * | | * ----------- */ uint8_t res; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); res = gbcpu->regs.rn.a; res = res << 1; res |= gbcpu->regs.rn.a >> 7; gbcpu->regs.rn.f = (gbcpu->regs.rn.a >> 7) << 4; gbcpu->regs.rn.a = res; } static void op_sla(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res << 1; gbcpu->regs.rn.f = (val >> 7) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_rr(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res >> 1; res |= (gbcpu->regs.rn.f & CF) << 3; gbcpu->regs.rn.f = (val & 1) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_rra(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t res; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); res = gbcpu->regs.rn.a; res = res >> 1; res |= (gbcpu->regs.rn.f & CF) << 3; gbcpu->regs.rn.f = (gbcpu->regs.rn.a & 1) << 4; gbcpu->regs.rn.a = res; } static void op_rrc(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res >> 1; res |= val << 7; gbcpu->regs.rn.f = (val & 1) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_rrca(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t res; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); res = gbcpu->regs.rn.a; res = res >> 1; res |= gbcpu->regs.rn.a << 7; gbcpu->regs.rn.f = (gbcpu->regs.rn.a & 1) << 4; gbcpu->regs.rn.a = res; } static void op_sra(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res >> 1; res |= val & 0x80; gbcpu->regs.rn.f = (val & 1) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_srl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint8_t res, val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); res = val = get_reg(gbcpu, reg); res = res >> 1; gbcpu->regs.rn.f = (val & 1) << 4; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static void op_swap(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op & 7; uint32_t res; uint32_t val; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); val = get_reg(gbcpu, reg); res = (val >> 4) | (val << 4); gbcpu->regs.rn.f = 0; if (res == 0) gbcpu->regs.rn.f |= ZF; put_reg(gbcpu, reg, res); } static const struct opinfo cbops[8] = { OPINFO("RLC", &op_rlc, 0, 0), /* opcode cb00-cb07 */ OPINFO("RRC", &op_rrc, 0, 0), /* opcode cb08-cb0f */ OPINFO("RL", &op_rl, 0, 0), /* opcode cb10-cb17 */ OPINFO("RR", &op_rr, 0, 0), /* opcode cb18-cb1f */ OPINFO("SLA", &op_sla, 0, 0), /* opcode cb20-cb27 */ OPINFO("SRA", &op_sra, 0, 0), /* opcode cb28-cb2f */ OPINFO("SWAP", &op_swap, 0, 0), /* opcode cb30-cb37 */ OPINFO("SRL", &op_srl, 0, 0), /* opcode cb38-cb3f */ }; static void op_cbprefix(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t pc = REGS16_R(gbcpu->regs, PC); UNUSED(oi); REGS16_W(gbcpu->regs, PC, pc + 1); op = mem_get(gbcpu, pc); DPRINTF("%02x", op); switch (op >> 6) { case 0: cbops[(op >> 3) & 7].fn(gbcpu, op, &cbops[(op >> 3) & 7]); return; case 1: op_bit(gbcpu, op); return; case 2: op_res(gbcpu, op); return; case 3: op_set(gbcpu, op); return; } fprintf(stderr, "\n\nUnknown CB subopcode %02x.\n", (unsigned char)op); gbcpu->stopped = 1; } static void op_ld(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long src = op & 7; long dst = (op >> 3) & 7; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(dst); DPRINTF(", "); print_reg(src); put_reg(gbcpu, dst, get_reg(gbcpu, src)); } static void op_ld_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long ofs = get_imm16(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, [0x%04lx]", oi->name, ofs); gbcpu->regs.rn.a = mem_get(gbcpu, ofs); } static void op_ld_ind16_a(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long ofs = get_imm16(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s [0x%04lx], A", oi->name, ofs); mem_put(gbcpu, ofs, gbcpu->regs.rn.a); } static void op_ld_ind16_sp(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long ofs = get_imm16(gbcpu); long sp = REGS16_R(gbcpu->regs, SP); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s [0x%04lx], SP", oi->name, ofs); mem_put(gbcpu, ofs, sp & 0xff); mem_put(gbcpu, ofs+1, sp >> 8); } static void op_ld_hlsp(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { int8_t ofs = get_imm8(gbcpu); uint16_t old = REGS16_R(gbcpu->regs, SP); uint16_t new = old + ofs; UNUSED(op); UNUSED(oi); if (ofs>0) DPRINTF(" \t%s HL, SP+0x%02x", oi->name, ofs); else DPRINTF(" \t%s HL, SP-0x%02x", oi->name, -ofs); REGS16_W(gbcpu->regs, HL, new); gbcpu->regs.rn.f = 0; /* flags are based on LOW-BYTE */ if ((old & 0xff) > (new & 0xff)) gbcpu->regs.rn.f |= CF; if ((old & 0xf) > (new & 0xf)) gbcpu->regs.rn.f |= HF; // 4 extra cycles. gbcpu->cycles += 4; } static void op_ld_sphl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); DPRINTF(" \t%s SP, HL", oi->name); REGS16_W(gbcpu->regs, SP, REGS16_R(gbcpu->regs, HL)); // 4 extra cycles. gbcpu->cycles += 4; } static void op_ld_reg16_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long val = get_imm16(gbcpu); long reg = (op >> 4) & 3; UNUSED(oi); reg += reg > 2; /* skip over AF */ DPRINTF(" \t%s %s, 0x%04lx", oi->name, regnamech16[reg], val); REGS16_W(gbcpu->regs, reg, val); } static void op_ld_reg16_a(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 4) & 3; uint16_t r; UNUSED(oi); reg -= reg > 2; /* for HL LDD LDI opcodes */ if (op & 8) { DPRINTF(" \t%s A, [%s]", oi->name, regnamech16[reg]); gbcpu->regs.rn.a = mem_get(gbcpu, r = REGS16_R(gbcpu->regs, reg)); } else { DPRINTF(" \t%s [%s], A", oi->name, regnamech16[reg]); mem_put(gbcpu, r = REGS16_R(gbcpu->regs, reg), gbcpu->regs.rn.a); } if (reg == 2) { r += (((op & 0x10) == 0) << 1)-1; REGS16_W(gbcpu->regs, reg, r); } } static void op_ld_reg8_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long val = get_imm8(gbcpu); long reg = (op >> 3) & 7; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); put_reg(gbcpu, reg, val); DPRINTF(", 0x%02lx", val); } static void op_ldh(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long ofs = op & 2 ? 0 : get_imm8(gbcpu); UNUSED(oi); if (op & 0x10) { DPRINTF(" \t%s A, ", oi->name); if ((op & 2) == 0) { DPRINTF("[%02lx]", ofs); } else { ofs = gbcpu->regs.rn.c; DPRINTF("[C]"); } gbcpu->regs.rn.a = mem_get(gbcpu, 0xff00 + ofs); } else { if ((op & 2) == 0) { DPRINTF(" \t%s [%02lx], A", oi->name, ofs); } else { ofs = gbcpu->regs.rn.c; DPRINTF(" \t%s [C], A", oi->name); } mem_put(gbcpu, 0xff00 + ofs, gbcpu->regs.rn.a); } } static void op_inc(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 3) & 7; uint8_t res; uint8_t old; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); old = res = get_reg(gbcpu, reg); res++; put_reg(gbcpu, reg, res); gbcpu->regs.rn.f &= ~(NF | ZF | HF); if (res == 0) gbcpu->regs.rn.f |= ZF; if ((old & 15) > (res & 15)) gbcpu->regs.rn.f |= HF; } static void op_inc16(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 4) & 3; uint16_t res; UNUSED(oi); reg += reg > 2; /* skip over AF */ res = REGS16_R(gbcpu->regs, reg); DPRINTF(" \t%s %s\t", oi->name, regnamech16[reg]); res++; REGS16_W(gbcpu->regs, reg, res); // 16bit ALU op takes 4 extra cycles. gbcpu->cycles += 4; } static void op_dec(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 3) & 7; uint8_t res; uint8_t old; UNUSED(oi); DPRINTF(" \t%s ", oi->name); print_reg(reg); old = res = get_reg(gbcpu, reg); res--; put_reg(gbcpu, reg, res); gbcpu->regs.rn.f |= NF; gbcpu->regs.rn.f &= ~(ZF | HF); if (res == 0) gbcpu->regs.rn.f |= ZF; if ((old & 15) < (res & 15)) gbcpu->regs.rn.f |= HF; } static void op_dec16(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 4) & 3; uint16_t res; UNUSED(oi); reg += reg > 2; /* skip over AF */ res = REGS16_R(gbcpu->regs, reg); DPRINTF(" \t%s %s", oi->name, regnamech16[reg]); res--; REGS16_W(gbcpu->regs, reg, res); // 16bit ALU op takes 4 extra cycles. gbcpu->cycles += 4; } static void op_add_sp_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { int8_t imm = get_imm8(gbcpu); uint16_t old = REGS16_R(gbcpu->regs, SP); uint16_t new = old; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s SP, %02x", oi->name, imm); new += imm; REGS16_W(gbcpu->regs, SP, new); gbcpu->regs.rn.f = 0; /* flags are based on LOW-BYTE */ if ((old & 0xff) > (new & 0xff)) gbcpu->regs.rn.f |= CF; if ((old & 0xf) > (new & 0xf)) gbcpu->regs.rn.f |= HF; // 8 extra cycles. gbcpu->cycles += 8; } static void op_add(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t old = gbcpu->regs.rn.a; uint8_t new; UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); gbcpu->regs.rn.a += get_reg(gbcpu, op & 7); new = gbcpu->regs.rn.a; gbcpu->regs.rn.f = 0; if (old > new) gbcpu->regs.rn.f |= CF; if ((old & 15) > (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_add_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); uint8_t old = gbcpu->regs.rn.a; uint8_t new = old; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); new += imm; gbcpu->regs.rn.a = new; gbcpu->regs.rn.f = 0; if (old > new) gbcpu->regs.rn.f |= CF; if ((old & 15) > (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_add_hl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = (op >> 4) & 3; uint16_t old = REGS16_R(gbcpu->regs, HL); uint16_t new = old; UNUSED(oi); reg += reg > 2; /* skip over AF */ DPRINTF(" \t%s HL, %s", oi->name, regnamech16[reg]); new += REGS16_R(gbcpu->regs, reg); REGS16_W(gbcpu->regs, HL, new); gbcpu->regs.rn.f &= ~(NF | CF | HF); if (old > new) gbcpu->regs.rn.f |= CF; if ((old & 0xfff) > (new & 0xfff)) gbcpu->regs.rn.f |= HF; // 4 extra cycles. gbcpu->cycles += 4; } static void op_adc(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t reg = get_reg(gbcpu, op & 7); uint8_t old = gbcpu->regs.rn.a; long new = old; long c = (gbcpu->regs.rn.f & CF) > 0; UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); new += reg; new += c; gbcpu->regs.rn.f = 0; gbcpu->regs.rn.a = new; if (new > 0xff) gbcpu->regs.rn.f |= CF; if ((old & 15) + (reg & 15) + c > 15) gbcpu->regs.rn.f |= HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_adc_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); uint8_t old = gbcpu->regs.rn.a; long new = old; long c = (gbcpu->regs.rn.f & CF) > 0; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); new += imm; new += c; gbcpu->regs.rn.f = 0; gbcpu->regs.rn.a = new; if (new > 0xff) gbcpu->regs.rn.f |= CF; if ((old & 15) + (imm & 15) + c > 15) gbcpu->regs.rn.f |= HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_cp(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t old = gbcpu->regs.rn.a; uint8_t new = old; UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); new -= get_reg(gbcpu, op & 7); gbcpu->regs.rn.f = NF; if (old < new) gbcpu->regs.rn.f |= CF; if ((old & 15) < (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_cp_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); uint8_t old = gbcpu->regs.rn.a; uint8_t new = old; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); new -= imm; gbcpu->regs.rn.f = NF; if (old < new) gbcpu->regs.rn.f |= CF; if ((old & 15) < (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_sub(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t old = gbcpu->regs.rn.a; uint8_t new; UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); gbcpu->regs.rn.a -= get_reg(gbcpu, op & 7); new = gbcpu->regs.rn.a; gbcpu->regs.rn.f = NF; if (old < new) gbcpu->regs.rn.f |= CF; if ((old & 15) < (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_sub_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); uint8_t old = gbcpu->regs.rn.a; uint8_t new = old; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); new -= imm; gbcpu->regs.rn.a = new; gbcpu->regs.rn.f = NF; if (old < new) gbcpu->regs.rn.f |= CF; if ((old & 15) < (new & 15)) gbcpu->regs.rn.f |= HF; if (new == 0) gbcpu->regs.rn.f |= ZF; } static void op_sbc(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t reg = get_reg(gbcpu, op & 7); uint8_t old = gbcpu->regs.rn.a; long new = old + 0x100; long c = (gbcpu->regs.rn.f & CF) > 0; UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); new -= reg; new -= c; gbcpu->regs.rn.a = new; gbcpu->regs.rn.f = NF; if (new < 0x100) gbcpu->regs.rn.f |= CF; if ((old & 15) - (reg & 15) - c < 0) gbcpu->regs.rn.f |= HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_sbc_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); uint8_t old = gbcpu->regs.rn.a; long new = old + 0x100; long c = (gbcpu->regs.rn.f & CF) > 0; UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); new -= imm; new -= c; gbcpu->regs.rn.a = new; gbcpu->regs.rn.f = NF; if (new < 0x100) gbcpu->regs.rn.f |= CF; if ((old & 15) - (imm & 15) - c < 0) gbcpu->regs.rn.f |= HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_and(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); gbcpu->regs.rn.a &= get_reg(gbcpu, op & 7); gbcpu->regs.rn.f = HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_and_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); gbcpu->regs.rn.a &= imm; gbcpu->regs.rn.f = HF; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_or(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); gbcpu->regs.rn.a |= get_reg(gbcpu, op & 7); gbcpu->regs.rn.f = 0; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_or_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); gbcpu->regs.rn.a |= imm; gbcpu->regs.rn.f = 0; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_xor(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(oi); DPRINTF(" \t%s A, ", oi->name); print_reg(op & 7); gbcpu->regs.rn.a ^= get_reg(gbcpu, op & 7); gbcpu->regs.rn.f = 0; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_xor_imm(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint8_t imm = get_imm8(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s A, $0x%02x", oi->name, imm); gbcpu->regs.rn.a ^= imm; gbcpu->regs.rn.f = 0; if (gbcpu->regs.rn.a == 0) gbcpu->regs.rn.f |= ZF; } static void op_push(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op >> 4 & 3; UNUSED(oi); push(gbcpu, REGS16_R(gbcpu->regs, reg)); // 4 extra cycles. gbcpu->cycles += 4; DPRINTF(" \t%s %s\t", oi->name, regnamech16[reg]); } static void op_push_af(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t tmp = gbcpu->regs.rn.a << 8; UNUSED(op); UNUSED(oi); tmp |= gbcpu->regs.rn.f; push(gbcpu, tmp); // 4 extra cycles. gbcpu->cycles += 4; DPRINTF(" \t%s %s\t", oi->name, regnamech16[op >> 4 & 3]); } static void op_pop(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long reg = op >> 4 & 3; UNUSED(oi); REGS16_W(gbcpu->regs, reg, pop(gbcpu)); DPRINTF(" \t%s %s\t", oi->name, regnamech16[reg]); } static void op_pop_af(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t tmp = pop(gbcpu); UNUSED(op); UNUSED(oi); gbcpu->regs.rn.f = tmp & 0xf0; gbcpu->regs.rn.a = tmp >> 8; DPRINTF(" \t%s %s\t", oi->name, regnamech16[op >> 4 & 3]); } static void op_cpl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); gbcpu->regs.rn.a = ~gbcpu->regs.rn.a; gbcpu->regs.rn.f |= NF | HF; } static void op_ccf(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); gbcpu->regs.rn.f ^= CF; gbcpu->regs.rn.f &= ~(NF | HF); } static void op_scf(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); gbcpu->regs.rn.f |= CF; gbcpu->regs.rn.f &= ~(NF | HF); } static void op_call(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t ofs = get_imm16(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s 0x%04x", oi->name, ofs); push(gbcpu, REGS16_R(gbcpu->regs, PC)); REGS16_W(gbcpu->regs, PC, ofs); // 4 extra cycles. gbcpu->cycles += 4; } static void op_call_cond(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t ofs = get_imm16(gbcpu); long cond = (op >> 3) & 3; UNUSED(oi); DPRINTF(" \t%s %s 0x%04x", oi->name, conds[cond], ofs); switch (cond) { case 0: if ((gbcpu->regs.rn.f & ZF) != 0) return; break; case 1: if ((gbcpu->regs.rn.f & ZF) == 0) return; break; case 2: if ((gbcpu->regs.rn.f & CF) != 0) return; break; case 3: if ((gbcpu->regs.rn.f & CF) == 0) return; break; } // A taken call is 4 extra cycles. gbcpu->cycles += 4; push(gbcpu, REGS16_R(gbcpu->regs, PC)); REGS16_W(gbcpu->regs, PC, ofs); } static void op_ret(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); REGS16_W(gbcpu->regs, PC, pop(gbcpu)); // 4 extra cycles. gbcpu->cycles += 4; DPRINTF(" \t%s", oi->name); } static void op_reti(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); gbcpu->ime = 1; REGS16_W(gbcpu->regs, PC, pop(gbcpu)); DPRINTF(" \t%s", oi->name); // 4 extra cycles. gbcpu->cycles += 4; } static void op_ret_cond(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long cond = (op >> 3) & 3; UNUSED(oi); // 4 extra cycles. gbcpu->cycles += 4; DPRINTF(" \t%s %s", oi->name, conds[cond]); switch (cond) { case 0: if ((gbcpu->regs.rn.f & ZF) != 0) return; break; case 1: if ((gbcpu->regs.rn.f & ZF) == 0) return; break; case 2: if ((gbcpu->regs.rn.f & CF) != 0) return; break; case 3: if ((gbcpu->regs.rn.f & CF) == 0) return; break; } // 4 extra cycles. gbcpu->cycles += 4; REGS16_W(gbcpu->regs, PC, pop(gbcpu)); } static void op_halt(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); gbcpu->halted = 1; DPRINTF(" \t%s", oi->name); } static void op_stop(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); } static void op_di(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); gbcpu->ime = 0; DPRINTF(" \t%s", oi->name); } static void op_ei(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); gbcpu->ime = 1; DPRINTF(" \t%s", oi->name); } static void op_jr(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { int16_t ofs = (int8_t) get_imm8(gbcpu); UNUSED(op); UNUSED(oi); if (ofs == -2 && gbcpu->ime == 0) { gbcpu->halted = 1; } if (ofs < 0) DPRINTF(" \t%s $-0x%02x", oi->name, -ofs); else DPRINTF(" \t%s $+0x%02x", oi->name, ofs); // 4 extra cycles. gbcpu->cycles += 4; REGS16_W(gbcpu->regs, PC, REGS16_R(gbcpu->regs, PC) + ofs); } static void op_jr_cond(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { int16_t ofs = (int8_t) get_imm8(gbcpu); long cond = (op >> 3) & 3; UNUSED(oi); if (ofs < 0) DPRINTF(" \t%s %s $-0x%02x", oi->name, conds[cond], -ofs); else DPRINTF(" \t%s %s $+0x%02x", oi->name, conds[cond], ofs); switch (cond) { case 0: if ((gbcpu->regs.rn.f & ZF) != 0) return; break; case 1: if ((gbcpu->regs.rn.f & ZF) == 0) return; break; case 2: if ((gbcpu->regs.rn.f & CF) != 0) return; break; case 3: if ((gbcpu->regs.rn.f & CF) == 0) return; break; } // A taken jump is 4 extra cycles. gbcpu->cycles += 4; REGS16_W(gbcpu->regs, PC, REGS16_R(gbcpu->regs, PC) + ofs); } static void op_jp(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t ofs = get_imm16(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s 0x%04x", oi->name, ofs); // 4 extra cycles. gbcpu->cycles += 4; REGS16_W(gbcpu->regs, PC, ofs); } static void op_jp_hl(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(op); UNUSED(oi); DPRINTF(" \t%s HL", oi->name); REGS16_W(gbcpu->regs, PC, REGS16_R(gbcpu->regs, HL)); } static void op_jp_cond(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { uint16_t ofs = get_imm16(gbcpu); long cond = (op >> 3) & 3; UNUSED(oi); DPRINTF(" \t%s %s 0x%04x", oi->name, conds[cond], ofs); switch (cond) { case 0: if ((gbcpu->regs.rn.f & ZF) != 0) return; break; case 1: if ((gbcpu->regs.rn.f & ZF) == 0) return; break; case 2: if ((gbcpu->regs.rn.f & CF) != 0) return; break; case 3: if ((gbcpu->regs.rn.f & CF) == 0) return; break; } // A taken jump is 4 extra cycles. gbcpu->cycles += 4; REGS16_W(gbcpu->regs, PC, ofs); } static void op_rst(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { int16_t ofs = op & 0x38; UNUSED(oi); DPRINTF(" \t%s 0x%02x", oi->name, ofs); push(gbcpu, REGS16_R(gbcpu->regs, PC)); REGS16_W(gbcpu->regs, PC, ofs); // 4 extra cycles. gbcpu->cycles += 4; } static void op_nop(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { UNUSED(gbcpu); UNUSED(op); UNUSED(oi); DPRINTF(" \t%s", oi->name); } static void op_daa(struct gbcpu* const gbcpu, uint32_t op, const struct opinfo *oi) { long a = gbcpu->regs.rn.a; long f = gbcpu->regs.rn.f; UNUSED(op); UNUSED(oi); if (f & NF) { if (f & HF) { a -= 0x06; a &= 0xff; } if (f & CF) a -= 0x60; } else { if (f & HF || (a & 0xf) > 9) a += 0x06; if (f & CF || a > 0x9f) a += 0x60; } f &= ~(HF | ZF); if (a > 0xff) f |= CF; a &= 0xff; if (a == 0) f |= ZF; gbcpu->regs.rn.a = (uint8_t)a; gbcpu->regs.rn.f = (uint8_t)f; DPRINTF(" \t%s", oi->name); } static const struct opinfo ops[256] = { OPINFO("NOP", &op_nop , 1, 1), /* opcode 00 */ OPINFO("LD", &op_ld_reg16_imm, 3, 3), /* opcode 01 */ OPINFO("LD", &op_ld_reg16_a , 2, 2), /* opcode 02 */ OPINFO("INC", &op_inc16 , 2, 2), /* opcode 03 */ OPINFO("INC", &op_inc , 1, 1), /* opcode 04 */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 05 */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 06 */ OPINFO("RLCA", &op_rlca , 1, 1), /* opcode 07 */ OPINFO("LD", &op_ld_ind16_sp , 5, 5), /* opcode 08 */ OPINFO("ADD", &op_add_hl , 2, 2), /* opcode 09 */ OPINFO("LD", &op_ld_reg16_a , 2, 2), /* opcode 0a */ OPINFO("DEC", &op_dec16 , 2, 2), /* opcode 0b */ OPINFO("INC", &op_inc , 1, 1), /* opcode 0c */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 0d */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 0e */ OPINFO("RRCA", &op_rrca , 1, 1), /* opcode 0f */ OPINFO("STOP", &op_stop , 0, 0), /* opcode 10 */ OPINFO("LD", &op_ld_reg16_imm, 3, 3), /* opcode 11 */ OPINFO("LD", &op_ld_reg16_a , 2, 2), /* opcode 12 */ OPINFO("INC", &op_inc16 , 2, 2), /* opcode 13 */ OPINFO("INC", &op_inc , 1, 1), /* opcode 14 */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 15 */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 16 */ OPINFO("RLA", &op_rla , 1, 1), /* opcode 17 */ OPINFO("JR", &op_jr , 3, 3), /* opcode 18 */ OPINFO("ADD", &op_add_hl , 2, 2), /* opcode 19 */ OPINFO("LD", &op_ld_reg16_a , 2, 2), /* opcode 1a */ OPINFO("DEC", &op_dec16 , 2, 2), /* opcode 1b */ OPINFO("INC", &op_inc , 1, 1), /* opcode 1c */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 1d */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 1e */ OPINFO("RRA", &op_rra , 1, 1), /* opcode 1f */ OPINFO("JR", &op_jr_cond , 2, 3), /* opcode 20 */ OPINFO("LD", &op_ld_reg16_imm, 3, 3), /* opcode 21 */ OPINFO("LDI", &op_ld_reg16_a , 2, 2), /* opcode 22 */ OPINFO("INC", &op_inc16 , 2, 2), /* opcode 23 */ OPINFO("INC", &op_inc , 1, 1), /* opcode 24 */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 25 */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 26 */ OPINFO("DAA", &op_daa , 1, 1), /* opcode 27 */ OPINFO("JR", &op_jr_cond , 2, 3), /* opcode 28 */ OPINFO("ADD", &op_add_hl , 2, 2), /* opcode 29 */ OPINFO("LDI", &op_ld_reg16_a , 2, 2), /* opcode 2a */ OPINFO("DEC", &op_dec16 , 2, 2), /* opcode 2b */ OPINFO("INC", &op_inc , 1, 1), /* opcode 2c */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 2d */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 2e */ OPINFO("CPL", &op_cpl , 1, 1), /* opcode 2f */ OPINFO("JR", &op_jr_cond , 2, 3), /* opcode 30 */ OPINFO("LD", &op_ld_reg16_imm, 3, 3), /* opcode 31 */ OPINFO("LDD", &op_ld_reg16_a , 2, 2), /* opcode 32 */ OPINFO("INC", &op_inc16 , 2, 2), /* opcode 33 */ OPINFO("INC", &op_inc , 3, 3), /* opcode 34 */ OPINFO("DEC", &op_dec , 3, 3), /* opcode 35 */ OPINFO("LD", &op_ld_reg8_imm , 3, 3), /* opcode 36 */ OPINFO("SCF", &op_scf , 1, 1), /* opcode 37 */ OPINFO("JR", &op_jr_cond , 2, 3), /* opcode 38 */ OPINFO("ADD", &op_add_hl , 2, 2), /* opcode 39 */ OPINFO("LDD", &op_ld_reg16_a , 2, 2), /* opcode 3a */ OPINFO("DEC", &op_dec16 , 2, 2), /* opcode 3b */ OPINFO("INC", &op_inc , 1, 1), /* opcode 3c */ OPINFO("DEC", &op_dec , 1, 1), /* opcode 3d */ OPINFO("LD", &op_ld_reg8_imm , 2, 2), /* opcode 3e */ OPINFO("CCF", &op_ccf , 1, 1), /* opcode 3f */ OPINFO("LD", &op_ld , 1, 1), /* opcode 40 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 41 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 42 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 43 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 44 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 45 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 46 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 47 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 48 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 49 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 4a */ OPINFO("LD", &op_ld , 1, 1), /* opcode 4b */ OPINFO("LD", &op_ld , 1, 1), /* opcode 4c */ OPINFO("LD", &op_ld , 1, 1), /* opcode 4d */ OPINFO("LD", &op_ld , 2, 2), /* opcode 4e */ OPINFO("LD", &op_ld , 1, 1), /* opcode 4f */ OPINFO("LD", &op_ld , 1, 1), /* opcode 50 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 51 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 52 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 53 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 54 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 55 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 56 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 57 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 58 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 59 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 5a */ OPINFO("LD", &op_ld , 1, 1), /* opcode 5b */ OPINFO("LD", &op_ld , 1, 1), /* opcode 5c */ OPINFO("LD", &op_ld , 1, 1), /* opcode 5d */ OPINFO("LD", &op_ld , 2, 2), /* opcode 5e */ OPINFO("LD", &op_ld , 1, 1), /* opcode 5f */ OPINFO("LD", &op_ld , 1, 1), /* opcode 60 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 61 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 62 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 63 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 64 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 65 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 66 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 67 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 68 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 69 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 6a */ OPINFO("LD", &op_ld , 1, 1), /* opcode 6b */ OPINFO("LD", &op_ld , 1, 1), /* opcode 6c */ OPINFO("LD", &op_ld , 1, 1), /* opcode 6d */ OPINFO("LD", &op_ld , 2, 2), /* opcode 6e */ OPINFO("LD", &op_ld , 1, 1), /* opcode 6f */ OPINFO("LD", &op_ld , 2, 2), /* opcode 70 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 71 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 72 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 73 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 74 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 75 */ OPINFO("HALT", &op_halt , 0, 0), /* opcode 76 */ OPINFO("LD", &op_ld , 2, 2), /* opcode 77 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 78 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 79 */ OPINFO("LD", &op_ld , 1, 1), /* opcode 7a */ OPINFO("LD", &op_ld , 1, 1), /* opcode 7b */ OPINFO("LD", &op_ld , 1, 1), /* opcode 7c */ OPINFO("LD", &op_ld , 1, 1), /* opcode 7d */ OPINFO("LD", &op_ld , 2, 2), /* opcode 7e */ OPINFO("LD", &op_ld , 1, 1), /* opcode 7f */ OPINFO("ADD", &op_add , 1, 1), /* opcode 80 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 81 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 82 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 83 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 84 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 85 */ OPINFO("ADD", &op_add , 2, 2), /* opcode 86 */ OPINFO("ADD", &op_add , 1, 1), /* opcode 87 */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 88 */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 89 */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 8a */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 8b */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 8c */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 8d */ OPINFO("ADC", &op_adc , 2, 2), /* opcode 8e */ OPINFO("ADC", &op_adc , 1, 1), /* opcode 8f */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 90 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 91 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 92 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 93 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 94 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 95 */ OPINFO("SUB", &op_sub , 2, 2), /* opcode 96 */ OPINFO("SUB", &op_sub , 1, 1), /* opcode 97 */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 98 */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 99 */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 9a */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 9b */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 9c */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 9d */ OPINFO("SBC", &op_sbc , 2, 2), /* opcode 9e */ OPINFO("SBC", &op_sbc , 1, 1), /* opcode 9f */ OPINFO("AND", &op_and , 1, 1), /* opcode a0 */ OPINFO("AND", &op_and , 1, 1), /* opcode a1 */ OPINFO("AND", &op_and , 1, 1), /* opcode a2 */ OPINFO("AND", &op_and , 1, 1), /* opcode a3 */ OPINFO("AND", &op_and , 1, 1), /* opcode a4 */ OPINFO("AND", &op_and , 1, 1), /* opcode a5 */ OPINFO("AND", &op_and , 2, 2), /* opcode a6 */ OPINFO("AND", &op_and , 1, 1), /* opcode a7 */ OPINFO("XOR", &op_xor , 1, 1), /* opcode a8 */ OPINFO("XOR", &op_xor , 1, 1), /* opcode a9 */ OPINFO("XOR", &op_xor , 1, 1), /* opcode aa */ OPINFO("XOR", &op_xor , 1, 1), /* opcode ab */ OPINFO("XOR", &op_xor , 1, 1), /* opcode ac */ OPINFO("XOR", &op_xor , 1, 1), /* opcode ad */ OPINFO("XOR", &op_xor , 2, 2), /* opcode ae */ OPINFO("XOR", &op_xor , 1, 1), /* opcode af */ OPINFO("OR", &op_or , 1, 1), /* opcode b0 */ OPINFO("OR", &op_or , 1, 1), /* opcode b1 */ OPINFO("OR", &op_or , 1, 1), /* opcode b2 */ OPINFO("OR", &op_or , 1, 1), /* opcode b3 */ OPINFO("OR", &op_or , 1, 1), /* opcode b4 */ OPINFO("OR", &op_or , 1, 1), /* opcode b5 */ OPINFO("OR", &op_or , 2, 2), /* opcode b6 */ OPINFO("OR", &op_or , 1, 1), /* opcode b7 */ OPINFO("CP", &op_cp , 1, 1), /* opcode b8 */ OPINFO("CP", &op_cp , 1, 1), /* opcode b9 */ OPINFO("CP", &op_cp , 1, 1), /* opcode ba */ OPINFO("CP", &op_cp , 1, 1), /* opcode bb */ OPINFO("CP", &op_cp , 1, 1), /* opcode bc */ OPINFO("CP", &op_cp , 1, 1), /* opcode bd */ OPINFO("CP", &op_cp , 2, 2), /* opcode be */ OPINFO("CP", &op_cp , 1, 1), /* opcode bf */ OPINFO("RET", &op_ret_cond , 2, 5), /* opcode c0 */ OPINFO("POP", &op_pop , 3, 3), /* opcode c1 */ OPINFO("JP", &op_jp_cond , 3, 4), /* opcode c2 */ OPINFO("JP", &op_jp , 4, 4), /* opcode c3 */ OPINFO("CALL", &op_call_cond , 3, 6), /* opcode c4 */ OPINFO("PUSH", &op_push , 4, 4), /* opcode c5 */ OPINFO("ADD", &op_add_imm , 2, 2), /* opcode c6 */ OPINFO("RST", &op_rst , 4, 4), /* opcode c7 */ OPINFO("RET", &op_ret_cond , 2, 5), /* opcode c8 */ OPINFO("RET", &op_ret , 4, 4), /* opcode c9 */ OPINFO("JP", &op_jp_cond , 3, 4), /* opcode ca */ OPINFO("CBPREFIX", &op_cbprefix, 0, 0), /* opcode cb */ OPINFO("CALL", &op_call_cond , 3, 6), /* opcode cc */ OPINFO("CALL", &op_call , 6, 6), /* opcode cd */ OPINFO("ADC", &op_adc_imm , 2, 2), /* opcode ce */ OPINFO("RST", &op_rst , 4, 4), /* opcode cf */ OPINFO("RET", &op_ret_cond , 2, 5), /* opcode d0 */ OPINFO("POP", &op_pop , 3, 3), /* opcode d1 */ OPINFO("JP", &op_jp_cond , 3, 4), /* opcode d2 */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode d3 */ OPINFO("CALL", &op_call_cond , 3, 6), /* opcode d4 */ OPINFO("PUSH", &op_push , 4, 4), /* opcode d5 */ OPINFO("SUB", &op_sub_imm , 2, 2), /* opcode d6 */ OPINFO("RST", &op_rst , 4, 4), /* opcode d7 */ OPINFO("RET", &op_ret_cond , 2, 5), /* opcode d8 */ OPINFO("RETI", &op_reti , 4, 4), /* opcode d9 */ OPINFO("JP", &op_jp_cond , 3, 4), /* opcode da */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode db */ OPINFO("CALL", &op_call_cond , 3, 6), /* opcode dc */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode dd */ OPINFO("SBC", &op_sbc_imm , 2, 2), /* opcode de */ OPINFO("RST", &op_rst , 4, 4), /* opcode df */ OPINFO("LDH", &op_ldh , 3, 3), /* opcode e0 */ OPINFO("POP", &op_pop , 3, 3), /* opcode e1 */ OPINFO("LDH", &op_ldh , 2, 2), /* opcode e2 */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode e3 */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode e4 */ OPINFO("PUSH", &op_push , 4, 4), /* opcode e5 */ OPINFO("AND", &op_and_imm , 2, 2), /* opcode e6 */ OPINFO("RST", &op_rst , 4, 4), /* opcode e7 */ OPINFO("ADD", &op_add_sp_imm , 4, 4), /* opcode e8 */ OPINFO("JP", &op_jp_hl , 1, 1), /* opcode e9 */ OPINFO("LD", &op_ld_ind16_a , 4, 4), /* opcode ea */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode eb */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode ec */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode ed */ OPINFO("XOR", &op_xor_imm , 2, 2), /* opcode ee */ OPINFO("RST", &op_rst , 4, 4), /* opcode ef */ OPINFO("LDH", &op_ldh , 3, 3), /* opcode f0 */ OPINFO("POP", &op_pop_af , 3, 3), /* opcode f1 */ OPINFO("LDH", &op_ldh , 2, 2), /* opcode f2 */ OPINFO("DI", &op_di , 1, 1), /* opcode f3 */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode f4 */ OPINFO("PUSH", &op_push_af , 4, 4), /* opcode f5 */ OPINFO("OR", &op_or_imm , 2, 2), /* opcode f6 */ OPINFO("RST", &op_rst , 4, 4), /* opcode f7 */ OPINFO("LD", &op_ld_hlsp , 3, 3), /* opcode f8 */ OPINFO("LD", &op_ld_sphl , 2, 2), /* opcode f9 */ OPINFO("LD", &op_ld_imm , 4, 4), /* opcode fa */ OPINFO("EI", &op_ei , 1, 1), /* opcode fb */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode fc */ OPINFO("UNKN", &op_unknown , 0, 0), /* opcode fd */ OPINFO("CP", &op_cp_imm , 2, 2), /* opcode fe */ OPINFO("RST", &op_rst , 4, 4), /* opcode ff */ }; #if DEBUG == 1 static void dump_regs(struct gbcpu* const gbcpu) { long i; DPRINTF("; "); for (i=0; i<8; i++) { DPRINTF("%c=%02x ", regnames[i], REGS8_R(gbcpu->regs, i)); } for (i=5; i<6; i++) { DPRINTF("%s=%04x ", regnamech16[i], REGS16_R(gbcpu->regs, i)); } DPRINTF("\n"); gbcpu->oldregs = gbcpu->regs; } static void show_reg_diffs(struct gbcpu* const gbcpu, const struct opinfo *oi) { long i; DPRINTF("\t\t; "); for (i=0; i<3; i++) { if (REGS16_R(gbcpu->regs, i) != REGS16_R(gbcpu->oldregs, i)) { DPRINTF("%s=%04x ", regnamech16[i], REGS16_R(gbcpu->regs, i)); REGS16_W(gbcpu->oldregs, i, REGS16_R(gbcpu->regs, i)); } } for (i=6; i<8; i++) { if (REGS8_R(gbcpu->regs, i) != REGS8_R(gbcpu->oldregs, i)) { if (i == 6) { /* Flags */ if (gbcpu->regs.rn.f & ZF) DPRINTF("Z"); else DPRINTF("z"); if (gbcpu->regs.rn.f & NF) DPRINTF("N"); else DPRINTF("n"); if (gbcpu->regs.rn.f & HF) DPRINTF("H"); else DPRINTF("h"); if (gbcpu->regs.rn.f & CF) DPRINTF("C"); else DPRINTF("c"); DPRINTF(" "); } else { DPRINTF("%c=%02x ", regnames[i], REGS8_R(gbcpu->regs,i)); } REGS8_W(gbcpu->oldregs, i, REGS8_R(gbcpu->regs, i)); } } for (i=4; i<5; i++) { if (REGS16_R(gbcpu->regs, i) != REGS16_R(gbcpu->oldregs, i)) { DPRINTF("%s=%04x ", regnamech16[i], REGS16_R(gbcpu->regs, i)); REGS16_W(gbcpu->oldregs, i, REGS16_R(gbcpu->regs, i)); } } DPRINTF(" %ld cycles", gbcpu->cycles); if (!CYCLES_OK(oi, gbcpu->cycles/4)) { DPRINTF(", but should be %d or %d!\n", 4*CYCLES1(oi), 4*CYCLES2(oi)); } DPRINTF("\n"); } #endif void gbcpu_add_mem(struct gbcpu* const gbcpu, uint32_t start, uint32_t end, gbcpu_put_fn putfn, gbcpu_get_fn getfn, void *priv) { uint32_t i; for (i=start; i<=end; i++) { gbcpu->putlookup[i].put = putfn; gbcpu->putlookup[i].priv = priv; gbcpu->getlookup[i].get = getfn; gbcpu->getlookup[i].priv = priv; } } void gbcpu_init(struct gbcpu* const gbcpu) { assert(sizeof(gbcpu->regs) == sizeof(gbcpu_regs_u)); memset(&gbcpu->regs, 0, sizeof(gbcpu->regs)); gbcpu->halted = 0; gbcpu->stopped = 0; gbcpu->ime = 0; gbcpu->halt_at_pc = -1; DEB(dump_regs(gbcpu)); } void gbcpu_intr(struct gbcpu* const gbcpu, long vec) { DPRINTF("gbcpu_intr(%04lx)\n", vec); gbcpu->halted = 0; gbcpu->ime = 0; push(gbcpu, REGS16_R(gbcpu->regs, PC)); REGS16_W(gbcpu->regs, PC, vec); } long gbcpu_step(struct gbcpu* const gbcpu) { uint8_t op; if (!gbcpu->halted) { op = mem_get(gbcpu, gbcpu->regs.rn.pc++); gbcpu->cycles = 4; DPRINTF("%04x: %02x", gbcpu->regs.rn.pc - 1, op); ops[op].fn(gbcpu, op, &ops[op]); DEB(show_reg_diffs(gbcpu, &ops[op])); if (gbcpu->halt_at_pc != -1 && REGS16_R(gbcpu->regs, PC) == gbcpu->halt_at_pc) { DPRINTF("halted at PC %04lx\n", gbcpu->halt_at_pc); gbcpu->halted = 1; gbcpu->ime = 1; } return gbcpu->cycles; } if (gbcpu->stopped) return -1; return 16; } gbsplay-0.0.102/contrib/0000755000175100017510000000000015105657176014376 5ustar runnerrunnergbsplay-0.0.102/contrib/gbs2ogg.sh0000644000175100017510000000253015105657176016264 0ustar runnerrunner#!/bin/sh # # Automatically convert all subsongs from .gbs file to .ogg files. # # 2003-2005,2008,2019 (C) by Christian Garbs # # Licensed under GNU GPL v1 or, at your option, any later version # FILENAME=$1 QUALITY=6 RATE=44100 PLAY=150 FADE=6 GAP=0 if [ -z "$1" ]; then echo "usage: gbs2ogg.sh [subsong_seconds]" exit 1 fi if [ "$2" ]; then PLAY=$2 fi FILEBASE=$(basename "$FILENAME") FILEBASE=$(echo "$FILEBASE"|sed 's/.gbs$//') # get subsong count # get song info gbsinfo "$FILENAME" | cut -d : -f 2- | sed -e 's/^ *//' -e 's/^"//' -e 's/"$//' | ( read -r _ #GBSVERSION read -r TITLE read -r AUTHOR read -r COPYRIGHT read -r _ #LOAD_ADDR read -r _ #INIT_ADDR read -r _ #PLAY_ADDR read -r _ #STACK_PTR read -r _ #FILE_SIZE read -r _ #ROM_SIZE read -r SUBSONGS for SUBSONG in $(seq 1 "$SUBSONGS"); do OGGFILE="$(printf "%s-%02d.ogg" "$FILEBASE" "$SUBSONG")" printf "== converting song %02d/%02d:\n" "$SUBSONG" "$SUBSONGS" gbsplay -o stdout -E l -r "$RATE" -g "$GAP" -f "$FADE" -t "$PLAY" "$FILENAME" "$SUBSONG" "$SUBSONG" # licensed under GNU GPL v1 or, at your option, any later version local gbsfiles='_files -g "*(.gbs|.gbs.gz)"' function _get_subsongs() { gbsinfo "$1" | grep -i "subsongs" | grep -oP "\\d+" } function _gbsplay_internal() { local options=( -E+'[endianness]:endian:(b\:big l\:little n\:native)' -f+'[set fadeout]:fadeout:' -g+'[set subsong gap]:subsong gap:' '(- :)'-h'[display help and exit]' -H+'[set output high-pass filter]:filter:((dmg\:"Gameboy Classic (default)" cgb\:"Gameboy Color" off\:"no filter"))' '(-L)'-l'[set loop mode to range]' '(-l)'-L'[set loop mode to single]' -o+'[select output plugin]:plugout:->plugout' -r+'[set samplerate in Hz]:samplerate:' -R+'[set refresh delay in ms]:refresh-delay:' -t+'[set subsong timeout in s]:subsong-timeout:' -T+'[set silence timeout in s]:silence-timeout:' '(-v)'-q'[be quieter, reduce verbosity]' # TODO: -qqq '(-q)'-v'[print more information, increase verbosity]' # TODO: -vvv '(- :)'-V'[display version number and exit]' '(-Z)'-z'[play subsongs in shuffle mode]' '(-z)'-Z'[play subsongs in random mode]' -1'[mute channel 1 on start]' -2'[mute channel 2 on start]' -3'[mute channel 3 on start]' -4'[mute channel 4 on start]' --'[end-of-options separator]' ": :${gbsfiles}" ':start_at_subsong:->subsong_start' ':stop_at_subsong:->subsong_stop' ) _arguments -s -S -A "-*" : $options case $state in plugout) local plugouts=$(gbsplay -o list 2>/dev/null | tail -n+2 | sed -E 's/^(\w+)\s+-\s+(.+)$/\1:"\2"/g') local -a optlist=(${(f)plugouts} 'list:"show available plugins"') _describe plugout optlist ;; subsong_start) # NOTE: assuming previous parameters are valid local max_subsongs=$(_get_subsongs "${(Q)words[-2]}") local -a subsongs=($(seq 1 $max_subsongs)) _describe start_at_subsong subsongs ;; subsong_stop) # NOTE: assuming previous parameters are valid local min_subsongs=${words[-2]} local max_subsongs=$(_get_subsongs "${(Q)words[-3]}") local -a subsongs=($(seq $min_subsongs $max_subsongs)) _describe stop_at_subsong subsongs ;; esac } function _gbsinfo_internal() { local options=( '(- :)'-h'[display help and exit]' '(- :)'-V'[display version number and exit]' ": :${gbsfiles}" ) _arguments -A "-*" : $options } function _gbs2ogg_internal() { local options=( ": :${gbsfiles}" ':subsong_seconds: ' ) _arguments : $options } function _gbs2gb_internal() { local options=( '(- :)'-h'[display help and exit]' '(- :)'-V'[display version number and exit]' -t'[rom template]:rom template:_files -g "*.gb"' ": :${gbsfiles}" ':out-file: ' ) _arguments -A "-*" : $options } case $service in gbsplay) _gbsplay_internal ;; gbsinfo) _gbsinfo_internal ;; gbs2ogg) _gbs2ogg_internal ;; gbs2gb) _gbs2gb_internal ;; esacgbsplay-0.0.102/contrib/gbsplay.bashcompletion0000644000175100017510000001346715105657176021003 0ustar runnerrunner#!/bin/bash # # gbsplay/gbsinfo bash completion # # 2008-2025 (C) Christian Garbs # licensed under GNU GPL v1 or, at your option, any later version # # originally based on the ditz bash completion code __gbsplay_add_spaces_to_compreply() { # add trailing spaces local i for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do COMPREPLY[i]="${COMPREPLY[$i]} " done } __gbsplay_expand_filename() { COMPREPLY=() local i ext local -a files for ext in .gbs .gbs.gz; do # get all matching files ending with $ext # FIXME: This will always fail for filenames containing a newline. # compgen produces per-line output and this can never work for those filenames. # I think bash simply can't handle newlines in any form of Programmable Completion. mapfile -t files < <( compgen -f -X "!*$ext" -- "$cur" ) # add trailing space to filenames for (( i=0; i < ${#files[@]}; i++ )); do if ! [ -d "${files[$i]}" ]; then # prevent directories from also showing up as files COMPREPLY[${#COMPREPLY[@]}]="${files[$i]} " fi done done # get all matching directories # FIXME: directories with newlines also don't work local -a dirs mapfile -t dirs < <( compgen -d -- "$cur" ) # add trailing slash to directories for (( i=0; i < ${#dirs[@]}; i++ )); do COMPREPLY[${#COMPREPLY[@]}]="${dirs[$i]}/" done } __gbsplay_expand_subsongs() { # The filename needs to be quoted to save us from bad surprises, # but this will make things like ~/foo.gbs fail because we also suppress Tilde Expansion. # Manually expand ~/ to make the most common case work. # FIXME: All other Tilde Expansions are unsupported and will not work. local filename="${COMP_WORDS[filepos]}" if [[ $filename =~ ^~/ ]]; then filename="${HOME}/${filename#*/}" fi if [ ! -r "$filename" ]; then __gbsplay_return_empty_completion return fi mapfile -t COMPREPLY < <( compgen -W "$(seq "$(gbsinfo "$filename" 2>/dev/null | grep ^Subsongs | cut -d: -f 2)" 2>/dev/null)" -- "$cur" ) __gbsplay_add_spaces_to_compreply } __gbsplay_return_empty_completion() { COMPREPLY=() } __gbsplay() # FIXME: gbsinfo don't do tilde expansion, eg. '~/foo.gbs' as a filename won't be found { local cur=${COMP_WORDS[COMP_CWORD]} local prev=${COMP_WORDS[$(( COMP_CWORD - 1))]} if [ "${cur:0:1}" = '-' ] && ! [ "$prev" = '--' ]; then # ==> looks like an option, return list of all options mapfile -t COMPREPLY < <( compgen -W "-E -f -g -h -H -l -L -o -q -r -R -t -T -v -V -z -Z -1 -2 -3 -4 --" -- "$cur" ) __gbsplay_add_spaces_to_compreply elif [[ "$prev" =~ ^-.*E$ ]]; then # ==> previous word ended with -E, return list of endians mapfile -t COMPREPLY < <( compgen -W "b l n" -- "$cur" ) __gbsplay_add_spaces_to_compreply elif [[ "$prev" =~ ^-.*f$ ]]; then # ==> previous word ended with -f, but fadeout is an integer that can't be completed __gbsplay_return_empty_completion elif [[ "$prev" =~ ^-.*g$ ]]; then # ==> previous word ended with -g, but subsong gap is an integer that can't be completed __gbsplay_return_empty_completion elif [[ "$prev" =~ ^-.*H$ ]]; then # ==> previous word ended with -H, return list of filters mapfile -t COMPREPLY < <( compgen -W "dmg cgb off" -- "$cur" ) __gbsplay_add_spaces_to_compreply elif [[ "$prev" =~ ^-.*o$ ]]; then # ==> previous word ended with -o, return list of output plugins mapfile -t COMPREPLY < <( compgen -W "$(gbsplay -o list 2>/dev/null | ( read -r; cut -d - -f 1 )) list" -- "$cur" ) __gbsplay_add_spaces_to_compreply elif [[ "$prev" =~ ^-.*r$ ]]; then # ==> previous word ended with -r, but samplerate is an integer that can't be completed __gbsplay_return_empty_completion elif [[ "$prev" =~ ^-.*R$ ]]; then # ==> previous word ended with -R, but refresh delay is an integer that can't be completed __gbsplay_return_empty_completion elif [[ "$prev" =~ ^-.*t$ ]]; then # ==> previous word ended with -t, but subsong timeout is an integer that can't be completed __gbsplay_return_empty_completion elif [[ "$prev" =~ ^-.*T$ ]]; then # ==> previous word ended with -T, but silence timeout is an integer that can't be completed __gbsplay_return_empty_completion else # calculate position of filename local filepos=1 check= while [ "${COMP_WORDS[filepos]:0:1}" = '-' ]; do check=${COMP_WORDS[$filepos]} if [[ "$check" =~ ^-.*[EfgHorRtT]$ ]]; then # jump over parameter to -o (( filepos++ )) fi (( filepos++ )) # immediately break on "end of options" [ "$check" = '' ] && break done local after_filepos=$(( COMP_CWORD - filepos )) if [ $after_filepos -eq 0 ] ; then # ==> this is the filename __gbsplay_expand_filename elif [ $after_filepos -eq 1 ] ; then # ==> this is the subsong start __gbsplay_expand_subsongs elif [ $after_filepos -eq 2 ] ; then # ==> this is the subsong stop... if [[ ${COMP_WORDS[COMP_CWORD - 1]} =~ ^[0-9]+$ ]] ; then # ...but only if subsong start was given before __gbsplay_expand_subsongs fi fi fi } __gbsinfo() { local cur=${COMP_WORDS[COMP_CWORD]} local prev=${COMP_WORDS[$(( COMP_CWORD - 1))]} if [ "${cur:0:1}" = '-' ] && ! [ "$prev" = '--' ]; then # ==> looks like an option, return list of all options mapfile -t COMPREPLY < <( compgen -W "-h -V --" -- "$cur" ) __gbsplay_add_spaces_to_compreply else # calculate position of filename local filepos=1 check= while [ "${COMP_WORDS[filepos]:0:1}" = '-' ]; do check=${COMP_WORDS[$filepos]} (( filepos++ )) # immediately break on "end of options" [ "$check" = '' ] && break done local after_filepos=$(( COMP_CWORD - filepos )) if [ $after_filepos -eq 0 ] ; then # ==> this is the filename __gbsplay_expand_filename fi fi } complete -F __gbsplay -o nospace gbsplay complete -F __gbsinfo -o nospace gbsinfo gbsplay-0.0.102/examples/0000755000175100017510000000000015105657176014554 5ustar runnerrunnergbsplay-0.0.102/examples/nightmode.gbs0000644000175100017510000004300015105657176017224 0ustar runnerrunnerGBS082NightmodeLaxity y0O{_ >O>O> !n*W*_!p*W6!c*_*W%V^mU8> !" V>O!|@*_*W%>O>O>@>h>Uiiiiiiii!fIT]!D A A !^I>h*i*i*i*i*i*i*i*i!_A A :"#C0A A >h2G 2Oxiyixiyixiyixiyi:"#11ÿ1D A A !^I>h*i*i*i*i*i*i*i*i!_A A *Cx1A A >h2G 2OxiyixiyixiyixiyiD +;11VV V;1;1y B©0 2é0(Z (,O !h>" O!@ ~w "y͏2">!^GʦP`=( (`L@_`L@e٧E3t٧E3c_e}D!f"pu8u 4o!!h*fo*_c( *k( *s({**u}D!h"pv8vi4p!!j*fo*_c( *k( *s({**v}D!j"pw8w4q!!l*fo*_c( *k( *s({**w}D!l"pg> |j2_!W" >^e`>wa>b>GQ_!S~dc>&ɯ^!!~rBN(E7_!HQ***ٯ*Gbb***!ˎV(l_!P *~@*|~٧(Gx惰xٯ Z!x!˖FrBˆ C٧DAGxW(O|y惰xrBxxٯrB ٧(xxrB ٧(x xrB 6٧(|GOِٙ >>x@xrB +٧(G7xٯ>y@yF +٧(G7xٯ>z@zqK +٧(G7xٯ~?>]xy!z˾N!z~˾W]>xyNLN<W<_z=ٯ!0>" " >i8٧( ٧ W>W=z_zG(>>!0Wr>]xy!z˾N!~ON(87_!HQ*##*Gbwb***!ˎV(O_ {7{٧(G{惰{ٯ\!{!˖FOˆ 1٧OG{惰{O{{ٯO cO #\G7\{{!WO _=`ٯ{{!{FʦPN(!!W!{ˎæP!^ !{^ʂP8 (JE_!sR_<~  < <<ٯ\!W٧ O!{~˾G!W^(˞\! y"x#a$b%,k#wVN;c 'B[r!-9DOYbks{MPlay2     " r 2  p3:5? $$@$C@ )*@"b$    $%&'()&'*/+,-.&'()041527863     7'$@@ @@@@@u1$hu1$hۅ %ۅ %PHPH#Eg#Eg'gVV WWgVVWWgVVuXXfYYuXZZ[ W[gV=[ WA[r[[[j\\=[\j\r[][j\\^\j\r[^[j\\_\j\gVV_Wr[5`[j\\`\j\\`aj\b=[bjcr[cqdj\\ceaj\&fpffj\r[g[j\r[ghj\r[ghj\hghj\=[ghj\=[hiijVjtkkValtkRmm5ntk?nn%otk/oootk!ppptk/oo?qtk~qr3rtkr s=stks+tt@:@@AAA BBSC^C@CKDD BDvEE)FFXGGHHXG?II"JrJJKKrJ&LLMrJ|M N`N$O^OOGP Q?IIIQrJQQwRrJRHSSTUvUUrJ/V{VWjWQQWrJ8XXYTUZZrJZ_[[XG\\c]XGpLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLp        pLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLp \  P \  P \  P \  P \  P \  P \  P \  pLPLPLpLPLPLpLPLPLpLPLPLPLpLPLPLPLPLPLpLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLp \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p pLPLPLPLPLPLPLPLPLPLPLPLPLPLPLP0 0LPLPLp       | \ \ \ \ \ p \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p | \ \ \ LPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLPLL?@7| \ \ \ \ \ \ \ @?| \ \ %P \ \ \ P \ P  \ p 00 00 00 00 00 00 00 0pLPLpLPLpLpLpLpLpLPLpLPLp&LPLpLPLpLpLPLPLpLpLPLp 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 p 00 00 00 00 00 00 00 0pLPLpLPLpLpLpLpLpLPLpLPLp&LPLpLPLpLpLPLPLpLpLPLp 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p 0 0 0 0 p 0 0 0 0 0 0 0 0 p P0 0 P!$&+-02-&!0-$!2-)&!pLPLPLpLPLPLpLPLPLpLPLPLP0Lp!S&-02-)&!p- LP&LL\-L\&L\-L\&LP-LP&LLP+LP&LL\+LP-LP&LL\-L\&L\-L\&LP-LP&LLP+LP&LL\-Lp. LP'LL\.L\'L\.L\'LP.LP'LLP0LP'LL\0LP-LP&LL\-L\&L\-L\&LP-LP&LLP+LP&LL\-LpLPLpLPLp$LPLp"LPLpLP$LpLPLpLPLpLPLp&LPLpLPLp$LPLp#LPLp&LPLpLPLp&LPLpLPLp 00 00 00 00 00 00 00   pLPLpLPLpLpLpLpLpLPLpLPLp&LPLpLPLpLpLPLPLpLp 0 0 0 0 0 0 0 0 0 0 0 0 0 0   p !&()-2-)(&!!&()-2-)(&!!$&+-2-+&$!!$&+-2-+&$!pLPLpLPLp&LPLP$LpLpLp#LpLp$LPLPLpLPLp&LPLpLPLp!LPLP#LpLp$LpLPLp&LPLPLpLPLp $&'+0+'&$$&'+0+'&$!&()-2-)(&!!&()-2-)(&!p 00 00 00 00 LLP 00 00 0p $&'+0+'&$$&'+0+'&$LpLPLpLPLp$LPLp"LPLpLP$LpLPLpLPLpLPLP0$LP&LL\$L\&L\$L\&Lp2S-)&!p!LPL|!Lp"LPL\"Lp!LPL|!Lp"LpLPLPLP!LPLPLP!LPL|!Lp&LP!L|&Lp!LPL|!Lp"LPLPLPLP!LPLPLpPpPp 00 00 00 00 0LLp!LPL|!Lp"LPL\"Lp!LPL|!Lp"LpLPLPLP!LPLPLP!LPL|!Lp&LP!L|&Lp!LPL|!L|!L\L|!L|!L\L|!LpPLp 0 0 0 0 0 0 0 0 0 0 0 0 LP LPLPLP LPLP LPLP LPLPLP LPLPLPLpLP'LpLP"Lp'LPLp"LPLp'LP"LpLP'LpLP"Lp'LP"LpLP'LpLP"Lp'LPLp"LPLp'LP"LpLP'LpLP"Lp'LP"Lp \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p 0 \ p LPLPLPLPLPLPLPLPLPLPLPLPLPLPLpLP'LpLP"Lp'LPLp"LPLp'LP"LpLP'LpLP"Lp(LP'LpLP'LpLP"Lp'LPLp"LPLp'LP"LpLP'LpLP"LpLPLp 00 00 00 00 00 00 00 0pLpL|L|LpLpL|L|Lp LpLpLpL|L|LpLpL|L|LpLpLp"Pp 00 00 00 00 00 00 00 0 pLpL|L|LpLp L|L| LpLp LpLpL|L|LpLpL|L|LpLpLpP" OO OO OO OO O O O O O O O Op) &!$!!$!)"&"&"@0L\LP!D!P$L\$LP$"!D! P OO OO OO OO OO OO OO O_p &"'""!$!)!$pLP!L\!LP"L\"LP!Q"S$D! p50-)$!0-)$!Lp!L\!LP!$D!P)L\)LP$&")D! P& OO OO OO OO OO OO OO O _p & '$ % )% q'S)P'LP&'L\'LP$ L\LPL\LP "$% OO OO OO OO OO OO OO O p &$!!$!$)q#S$ D!LLpL|Lp!L|!LpL|Lp"L|"Lp!L\!LPL\LpL|L OOO_ OOO_ O_ O_ _ O_ _ OO S@D!OSLLpLLPLLPLLP LLPLLPD!S@LLp0L|0Lp)LLp000 0!0 0!0LLpS@LLgbsplay-0.0.102/examples/gbsplayrc_sample0000644000175100017510000000105715105657176020031 0ustar runnerrunner# # Example gbsplay configuration file # endian = n # native endian fadeout = 3 # seconds loop = 0 output_plugin = oss rate = 48000 # this is a comment refresh_delay = 33 # milliseconds silence_timeout = 2 # seconds subsong_gap = 2 # seconds subsong_timeout = 120 # seconds verbosity=0 # should work without whitespace too # '\' can be used as escape and tabs are whitespace subsong_timeout \ = \ # whee \1\2\0 # this works too, but you probably should not rely on that subsong_timeout = 200 gbsplay-0.0.102/depend.sh0000755000175100017510000000120715105657176014534 0ustar runnerrunner#!/bin/sh # # gbsplay is a Gameboy sound player # # 2003-2004,2019 (C) by Tobias Diedrich # Christian Garbs # Licensed under GNU GPL v1 or, at your option, any later version. # DIR=$(dirname "$1") FILE="$1" SUBMK="" shift EXTRADEP=" $*" if [ "$DIR" = "" ] || [ "$DIR" = "." ]; then DIR="" else DIR="$DIR/" fi if [ -f "${DIR}subdir.mk" ]; then SUBMK=" ${DIR}subdir.mk" fi exec "$CC" -M $GBSCFLAGS "$FILE" | sed -n -e " s@^\\(.*\\)\\.o:@$DIR\\1.d $DIR\\1.o $DIR\\1.lo: depend.sh Makefile$SUBMK$EXTRADEP@ s@/usr/[^ ]*@@g t foo :foo s@^[ ]*\\\\\$@@ t p " gbsplay-0.0.102/plugout_sdl.c0000644000175100017510000000455515105657176015454 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * SDL2 sound output plugin * * 2020-2024 (C) by Christian Garbs * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include /* SDL_HINT_APP_NAME was only added in SDL 2.0.18 */ #ifndef SDL_HINT_APP_NAME #define SDL_HINT_APP_NAME "SDL_APP_NAME" #endif #include "common.h" #include "plugout.h" #define PLAYBACK_MODE 0 #define NO_CHANGES_ALLOWED 0 #define UNPAUSE 0 #ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE #define SDL_FLAGS SDL_AUDIO_ALLOW_SAMPLES_CHANGE #else #define SDL_FLAGS NO_CHANGES_ALLOWED #endif int device; SDL_AudioSpec obtained; static long sdl_open(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata) { SDL_AudioSpec desired; if (SDL_Init(SDL_INIT_AUDIO) != 0) { fprintf(stderr, _("Could not init SDL: %s\n"), SDL_GetError()); return -1; } SDL_SetHint(SDL_HINT_APP_NAME, metadata.player_name); SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME, metadata.filename); SDL_zero(desired); desired.freq = rate; desired.channels = 2; desired.samples = 1024; desired.callback = NULL; switch (*endian) { case PLUGOUT_ENDIAN_BIG: desired.format = AUDIO_S16MSB; break; case PLUGOUT_ENDIAN_LITTLE: desired.format = AUDIO_S16LSB; break; default: desired.format = AUDIO_S16SYS; break; } device = SDL_OpenAudioDevice(NULL, PLAYBACK_MODE, &desired, &obtained, SDL_FLAGS); if (device == 0) { fprintf(stderr, _("Could not open SDL audio device: %s\n"), SDL_GetError()); return -1; } SDL_PauseAudioDevice(device, UNPAUSE); *buffer_bytes = obtained.samples * 4; return 0; } static ssize_t sdl_write(const void *buf, size_t count) { int overqueued = SDL_GetQueuedAudioSize(device) - obtained.size; float delaynanos = (float)overqueued / 4.0 / obtained.freq * 1000000000.0; struct timespec interval = {.tv_sec = 0, .tv_nsec = (long)delaynanos}; if (overqueued > 0) { nanosleep(&interval, NULL); } if (SDL_QueueAudio(device, buf, count) != 0) { fprintf(stderr, _("Could not write SDL audio data: %s\n"), SDL_GetError()); return -1; } return count; } static void sdl_close() { SDL_Quit(); } const struct output_plugin plugout_sdl = { .name = "sdl", .description = "SDL sound driver", .open = sdl_open, .write = sdl_write, .close = sdl_close, }; gbsplay-0.0.102/gbsformat.txt0000644000175100017510000000106515105657176015465 0ustar runnerrunner MAIN HEADER FIELDS Offset Size Description ====== ==== ========================== 00 3 Identifier string ("GBS") 03 1 Version (1) 04 1 Number of songs (1-255) 05 1 First song (usually 1) 06 2 Load address ($400-$7fff) 08 2 Init address ($400-$7fff) 0a 2 Play address ($400-$7fff) 0c 2 Stack pointer 0e 1 Timer modulo (see TIMING) 0f 1 Timer control (see TIMING) 10 32 Game Title string 30 32 Author string 50 32 Copyright string 70 n Code and Data (see RST VECTORS) gbsplay-0.0.102/plugout_dsound.c0000644000175100017510000001433015105657176016156 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * header file for DirectSound output under Windows, aquanight * This is C++ because COM is a pain in pure C * * 2003-2020 (C) by Christian Garbs * Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include #include #include "common.h" #include "plugout.h" static LPDIRECTSOUND8 dsound_device; static LPDIRECTSOUNDBUFFER8 dsound_buffer; static DWORD writeOffset; // Location of the last byte written (not the write cursor) static DWORD writeMax; // Maximum amount we can write at once. static WAVEFORMATEX wfx; static DSBUFFERDESC dsbdesc; static long dsound_open(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata) { HRESULT hr; UNUSED(metadata); *endian = PLUGOUT_ENDIAN_NATIVE; hr = DirectSoundCreate8(NULL, &dsound_device, NULL); if (FAILED(hr)) { switch (hr) { case DSERR_ALLOCATED: fprintf(stderr, "Failed to open DirectSound: the audio device is already in use by another program\n"); break; case DSERR_NOAGGREGATION: fprintf(stderr, "Failed to open DirectSound: aggregation not supported.\n"); break; case DSERR_NODRIVER: fprintf(stderr, "Failed to open DirectSound: no driver is available.\n"); break; case DSERR_OUTOFMEMORY: fprintf(stderr, "Failed to open DirectSound: the system is out of memory.\n"); break; default: fprintf(stderr, "Failed to open DirectSound: HRESULT = %lx\n", (unsigned long)hr); break; } return -1; } /* FUCKING FUCKITY FUCK I HAVE TO CREATE A WINDOW FOR THIS. */ hr = IDirectSound8_SetCooperativeLevel(dsound_device, GetDesktopWindow(), DSSCL_NORMAL); // XXX VERY MUCH NOT ACCEPTABLE TO USE AN HWND THAT'S NOT OURS. But should work for initial trials at least. if (FAILED(hr)) { switch (hr) { case DSERR_ALLOCATED: fprintf(stderr, "This shouldn't happen because we're not asking for anything fancy, so it should've already failed at device creation.\n"); break; case DSERR_UNSUPPORTED: case DSERR_UNINITIALIZED: fprintf(stderr, "Wat. "); break; default: fprintf(stderr, "SetCooperativeLevel failed: HRESULT = %lx\n", (unsigned long)hr); break; } IDirectSound8_Release(dsound_device); return -1; } /* Needed to get a streaming secondary sound buffer... */ memset(&wfx, 0, sizeof(WAVEFORMATEX)); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = 2; wfx.nSamplesPerSec = rate; wfx.nBlockAlign = 4; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; wfx.wBitsPerSample = 16; wfx.cbSize = 0; /* It's ignored anyway, but... */ memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); dsbdesc.dwSize = sizeof(DSBUFFERDESC); dsbdesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY; /* Keep sound buffer going if inactive, use accurate play cursor */ /* Round off buffer size to sample boundary */ writeMax = dsbdesc.dwBufferBytes = (wfx.nAvgBytesPerSec / 10) & ~(wfx.nBlockAlign - 1); dsbdesc.lpwfxFormat = &wfx; *buffer_bytes = writeMax; return 0; /* We're open, rock and roll! */ } static ssize_t dsound_write(const void* buf, size_t count) { size_t orig_count = count; DWORD playCursor, writeCursor; DWORD writeLimit; void* pBuf1; void* pBuf2; DWORD nBuf1, nBuf2; while (count > 0) { IDirectSoundBuffer8_GetCurrentPosition(dsound_buffer, &playCursor, &writeCursor); if (writeOffset == -1) { /* Initial buffering */ writeOffset = writeCursor; writeLimit = writeMax - writeOffset; } else if (writeOffset <= playCursor) { /* We have wrapped behind the play cursor. */ writeLimit = (playCursor - writeOffset); } else { writeLimit = writeMax - (writeOffset - playCursor); } if (writeLimit < 1) { continue; } if (count < writeLimit) { writeLimit = count; } IDirectSoundBuffer8_Lock(dsound_buffer, writeOffset, writeLimit, &pBuf1, &nBuf1, &pBuf2, &nBuf2, 0); assert(pBuf1 != NULL); memcpy(pBuf1, buf, nBuf1); if (pBuf2) { /* DirectSound gave us a wraparound pointer because the requested lock extends past the end of the buffer */ memcpy(pBuf2, (void*)((char*)buf + nBuf1), nBuf2); writeOffset = nBuf2; /* NOT += */ } else { /* All requested data fits buffer without wraparound; make sure we don't start the next request right at the end of the buffer (reset to 0 instead) */ assert((writeOffset + nBuf1) <= writeMax); writeOffset = (writeOffset + nBuf1) % writeMax; } IDirectSoundBuffer8_Unlock(dsound_buffer, pBuf1, nBuf1, pBuf2, nBuf2); if (writeLimit < count) { count -= writeLimit; buf = (void*)((char*)buf + writeLimit); } else count = 0; IDirectSoundBuffer8_Play(dsound_buffer, 0, 0, DSBPLAY_LOOPING); } return orig_count; } static int dsound_skip(int subsong) { HRESULT hr; LPDIRECTSOUNDBUFFER pDsb = NULL; if (dsound_buffer) { IDirectSoundBuffer8_Stop(dsound_buffer); /* Well that should be simple enough. */ IDirectSoundBuffer8_Release(dsound_buffer); dsound_buffer = NULL; } writeOffset = -1; hr = IDirectSound8_CreateSoundBuffer(dsound_device, &dsbdesc, &pDsb, NULL); if (FAILED(hr)) { return -1; } IDirectSoundBuffer_QueryInterface(pDsb, &IID_IDirectSoundBuffer8, (LPVOID*)&dsound_buffer); IDirectSoundBuffer_Release(pDsb); return 0; } static void dsound_pause(int pause_mode) { if (pause_mode && dsound_buffer) IDirectSoundBuffer8_Stop(dsound_buffer); if (!pause_mode && dsound_buffer) IDirectSoundBuffer8_Play(dsound_buffer, 0, 0, DSBPLAY_LOOPING); } static void dsound_close() { if (dsound_buffer) { IDirectSoundBuffer8_Stop(dsound_buffer); IDirectSoundBuffer8_Release(dsound_buffer); } if (dsound_device) IDirectSound8_Release(dsound_device); } static char plugout_dsound_name[] = "dsound"; static char plugout_dsound_desc[] = "DirectSound sound driver"; const struct output_plugin plugout_dsound = { .name = plugout_dsound_name, .description = plugout_dsound_desc, .open = dsound_open, .write = dsound_write, .pause = dsound_pause, .skip = dsound_skip, .close = dsound_close }; gbsplay-0.0.102/gbsinfo.c0000644000175100017510000000273515105657176014540 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2003-2025 (C) by Tobias Diedrich * Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include "common.h" #include "gbhw.h" #include "gbcpu.h" #include "libgbs.h" #include "gbs_internal.h" /* global variables */ char *myname; void usage(long exitcode) { FILE *out = exitcode ? stderr : stdout; fprintf(out, _("Usage: %s [OPTION]... [--] \n" "\n" "Available options are:\n" " -h display this help and exit\n" " -V print version and exit\n" " -- end options, next argument is GBS-FILE\n"), myname); exit(exitcode); } void version(void) { (void)puts("gbsplay " GBS_VERSION); exit(0); } void parseopts(int *argc, char ***argv) { long res; myname = *argv[0]; while ((res = getopt(*argc, *argv, "hV")) != -1) { switch (res) { default: usage(1); break; case 'h': usage(0); break; case 'V': version(); break; } } *argc -= optind; *argv += optind; } int main(int argc, char **argv) { struct gbs *gbs; i18n_init(); parseopts(&argc, &argv); if (argc < 1) { usage(1); } if ((gbs = gbs_open(argv[0])) == NULL) exit(EXIT_FAILURE); gbs_internal_api.print_info(gbs, 1); gbs_close(gbs); return 0; } gbsplay-0.0.102/xgbsplay.c0000644000175100017510000004000115105657176014726 0ustar runnerrunner/* * xgbsplay is an X11 frontend for gbsplay, the Gameboy sound player * * 2003-2025 (C) by Tobias Diedrich * Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "player.h" #define XCB_STRING_FORMAT 8 /* 8, 16 or 32 (bits per character?) */ #define XCB_EVENT_SYNTHETIC 0x80 #define XCB_EVENT_MASK (~(XCB_EVENT_SYNTHETIC)) /* * Virtual LCD has double the real Gameboy pixel count for usability * With an 8x16 font this gives us */ #define VLCD_WIDTH 320 #define VLCD_HEIGHT 288 /* Outer padding size */ #define VLCD_OUT_HPAD 14 #define VLCD_OUT_VPAD 13 /* Inner padding size */ #define VLCD_IN_HPAD 36 #define VLCD_IN_VPAD 35 /* LCD decoration (from outer) */ #define VLCD_DECO_HPAD 14 #define VLCD_DECO_VPAD 13 /* Top decoration (from edge) */ #define TOP_DECO_PAD 14 #define TOP_DECO_SIZE 4 #define MIN_WIDTH (VLCD_WIDTH + 2 * (VLCD_OUT_HPAD + VLCD_IN_HPAD)) #define MIN_HEIGHT (120 + TOP_DECO_PAD + TOP_DECO_SIZE + VLCD_HEIGHT + 2 * (VLCD_OUT_VPAD + VLCD_IN_VPAD)) #define STATUSTEXT_LENGTH 256 /* global variables */ static char statustext[STATUSTEXT_LENGTH]; static long quit = 0; static long screen_dirty = 0; static long screen_modified = 0; static uint32_t window_width = MIN_WIDTH; static uint32_t window_height = MIN_HEIGHT; static xcb_get_keyboard_mapping_reply_t *keymap; static xcb_connection_t *conn; static xcb_screen_t *screen; static xcb_window_t window; static cairo_surface_t *surface; static cairo_t *cr; static cairo_matrix_t default_matrix; static xcb_atom_t atomWmDeleteWindow = XCB_ATOM_NONE; static xcb_atom_t atomWmProtocols = XCB_ATOM_NONE; static xcb_atom_t atomWmName = XCB_ATOM_NONE; static const struct gbs_metadata *metadata; static const struct gbs_status *status; static struct displaytime displaytime; static long last_seconds = -1; static long last_subsong = -1; static long has_status_changed(struct gbs *gbs) { status = gbs_get_status(gbs); update_displaytime(&displaytime, status); if (displaytime.played_sec != last_seconds || status->subsong != last_subsong) { last_seconds = displaytime.played_sec; last_subsong = status->subsong; return 1; } return 0; } static void update_title() { int len; len = snprintf(statustext, STATUSTEXT_LENGTH, /* or use sizeof(statustext) ?? */ "xgbsplay %s %d/%d " "%02ld:%02ld/%02ld:%02ld" "%s", filename, status->subsong+1, status->songs, displaytime.played_min, displaytime.played_sec, displaytime.total_min, displaytime.total_sec, get_pause_string() ); xcb_icccm_set_wm_name(conn, window, XCB_ATOM_STRING, XCB_STRING_FORMAT, len, statustext); xcb_flush(conn); } void exit_handler(int signum) { printf(_("\nCaught signal %d, exiting...\n"), signum); exit(1); } static void draw_top(double pad, double height) { double bottom = pad + height; double left = 0; double right = MIN_WIDTH; double top = 0; cairo_set_line_width(cr, 1.0); // #6a4f41 cairo_set_source_rgb(cr, 0x6a/255.0, 0x4f/255.0, 0x41/255.0); cairo_move_to(cr, left, top + pad); cairo_line_to(cr, left + pad, top + pad); cairo_move_to(cr, left + bottom, top); cairo_line_to(cr, left + bottom, top + pad); cairo_line_to(cr, right - bottom, top + pad); cairo_move_to(cr, right - pad, top); cairo_line_to(cr, right - pad, top + pad); cairo_line_to(cr, right, top + pad); cairo_stroke(cr); // #dac1ad cairo_set_source_rgb(cr, 0xda/255.0, 0xc1/255.0, 0xad/255.0); cairo_move_to(cr, left + pad, top); cairo_line_to(cr, left + pad, top + pad); cairo_move_to(cr, right - bottom, top); cairo_line_to(cr, right - bottom, top + pad); cairo_move_to(cr, left, top + bottom); cairo_line_to(cr, right, top + bottom); cairo_stroke(cr); } static void draw_screen_outer(double left, double top, double r1, double r2) { double right = MIN_WIDTH - left; // double width = MIN_WIDTH - 2 * left; double height = VLCD_HEIGHT + 2 * VLCD_IN_VPAD; double bottom = top + height; // #ad484e cairo_set_source_rgb(cr, 0x4d/255.0, 0x48/255.0, 0x4e/255.0); cairo_arc(cr, left + r1, top + r1, r1, 2*(M_PI/2), 3*(M_PI/2)); cairo_arc(cr, right - r1, top + r1, r1, 3*(M_PI/2), 4*(M_PI/2)); cairo_arc(cr, right - r2, bottom - r2, r2, 0*(M_PI/2), 1*(M_PI/2)); cairo_arc(cr, left + r1, bottom - r1, r1, 1*(M_PI/2), 2*(M_PI/2)); cairo_close_path(cr); cairo_fill(cr); } static void draw_screen_inner(double left, double top) { double width = MIN_WIDTH - 2 * left; double height = width * 144.0 / 160.0; // #917702 cairo_set_source_rgb(cr, 0x91/255.0, 0x77/255.0, 0x02/255.0); cairo_rectangle(cr, left, top, width, height); cairo_fill(cr); } static void draw_screen_deco(double left, double top, double dist) { double right = MIN_WIDTH - left; double bottom = top + dist; cairo_set_line_width(cr, 2.0); // #62223a cairo_set_source_rgb(cr, 0x62/255.0, 0x22/255.0, 0x3a/255.0); cairo_move_to(cr, left, top); cairo_line_to(cr, right, top); cairo_stroke(cr); // #151333 cairo_set_source_rgb(cr, 0x15/255.0, 0x13/255.0, 0x33/255.0); cairo_move_to(cr, left, bottom); cairo_line_to(cr, right, bottom); cairo_stroke(cr); /* "DOT MATRIX" // #99949a cairo_set_source_rgb(cr, 0x99/255.0, 0x94/255.0, 0x9a/255.0); */ } static void draw_screen_line(double vx, double vy, const char *s) { cairo_move_to(cr, 50 + 8 * vx, 70 + 16 + 16 * vy); cairo_show_text(cr, s); cairo_stroke(cr); } static void draw_screen_linef(double vx, double vy, const char *fmt, ...) { char buf[41]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); buf[sizeof(buf) - 1] = 0; draw_screen_line(vx, vy, buf); } static void draw_screen_content() { // #0b433a cairo_set_source_rgb(cr, 0x0b/255.0, 0x43/255.0, 0x3a/255.0); cairo_select_font_face(cr, "Biwidth", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 16); draw_screen_line(0, 0, metadata->title); draw_screen_line(0, 1, metadata->author); draw_screen_line(0, 2, metadata->copyright); draw_screen_linef(0, 4, "Song %3d/%3d", status->subsong+1, status->songs); draw_screen_linef(0, 5, "%02ld:%02ld/%02ld:%02ld%s", displaytime.played_min, displaytime.played_sec, displaytime.total_min, displaytime.total_sec, get_pause_string()); draw_screen_linef(0, 6, " %s", get_loopmode_string(status)); draw_screen_line(0, 8, _("[p]revious/[n]ext subsong [q]uit")); draw_screen_line(0, 9, _("[1-4] mute channel [l]oop mode")); draw_screen_line(0, 10, _("[ ] pause/resume")); } static void redraw() { // #a9988e cairo_set_source_rgb(cr, 0xa8/255.0, 0x98/255.0, 0x8e/255.0); cairo_paint(cr); draw_top(TOP_DECO_PAD, TOP_DECO_SIZE); draw_screen_outer(VLCD_OUT_HPAD, TOP_DECO_PAD + TOP_DECO_SIZE + VLCD_OUT_VPAD, 15 /* small corner radius */, 50 /* big corner radius */); draw_screen_deco(VLCD_OUT_HPAD + VLCD_DECO_HPAD, TOP_DECO_PAD + TOP_DECO_SIZE + VLCD_OUT_VPAD + VLCD_DECO_VPAD, 8 /* deco height */); draw_screen_inner(VLCD_OUT_HPAD + VLCD_IN_HPAD, TOP_DECO_PAD + TOP_DECO_SIZE + VLCD_OUT_VPAD + VLCD_IN_VPAD); draw_screen_content(); // #191a41 cairo_set_source_rgb(cr, 0x19/255.0, 0x1a/255.0, 0x41/255.0); cairo_move_to(cr, 14, 445 + 30); cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 30); cairo_show_text(cr, "XGbsplay"); cairo_stroke(cr); cairo_identity_matrix(cr); cairo_scale(cr, (float)window_width / (float)MIN_WIDTH, (float)window_height / (float)MIN_HEIGHT); cairo_surface_flush(surface); } static int handle_button(xcb_button_release_event_t *bev, struct gbs *gbs) { fprintf(stderr, "release root: %d,%d event: %d,%d\n", bev->root_x, bev->root_y, bev->event_x, bev->event_y); /* TODO: Have things to click on */ /* FIXME: All clicks fall through to the root window and might trigger unseen things */ return 0; } static void icccm_init(xcb_window_t win) { xcb_size_hints_t size_hints; static const char name[] = "xgbsplay"; xcb_atom_t protocols[] = { atomWmDeleteWindow }; memset(&size_hints, 0, sizeof(size_hints)); xcb_icccm_set_wm_protocols(conn, win, atomWmProtocols, ARRAY_SIZE(protocols), protocols); xcb_icccm_set_wm_name(conn, win, XCB_ATOM_STRING, XCB_STRING_FORMAT, strlen(name), name); xcb_icccm_size_hints_set_aspect(&size_hints, MIN_WIDTH, MIN_HEIGHT, MIN_WIDTH, MIN_HEIGHT); xcb_icccm_size_hints_set_min_size(&size_hints, MIN_WIDTH, MIN_HEIGHT); xcb_icccm_set_wm_size_hints(conn, window, XCB_ATOM_WM_NORMAL_HINTS, &size_hints); } static xcb_visualtype_t *find_visual(xcb_screen_t *screen) { xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen); for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data); for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { if (screen->root_visual == visual_iter.data->visual_id) { return visual_iter.data; } } } return NULL; } static xcb_intern_atom_cookie_t request_atom(const char *name) { return xcb_intern_atom_unchecked(conn, 0 /* create if it does not exist */, strlen(name), name); } static const char *debug_atom_name(xcb_atom_t atom) { xcb_get_atom_name_cookie_t c = xcb_get_atom_name_unchecked(conn, atom); xcb_get_atom_name_reply_t *ar = xcb_get_atom_name_reply(conn, c, NULL); static char buf[23]; const char *name; size_t name_len; if (atom == XCB_ATOM_NONE) { return "XCB_ATOM_NONE"; } if (!ar) { return ""; } name = xcb_get_atom_name_name(ar); name_len = xcb_get_atom_name_name_length(ar); if (name_len > sizeof(buf) - 1) { name_len = sizeof(buf) - 1; } strncpy(buf, name, name_len); buf[name_len] = 0; free(ar); return buf; } static xcb_atom_t get_atom(xcb_intern_atom_cookie_t cookie) { xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(conn, cookie, NULL); xcb_atom_t atom; if (!r) { return XCB_ATOM_NONE; } atom = r->atom; free(r); return atom; } static void setup_atoms() { xcb_intern_atom_cookie_t atomc[3]; atomc[0] = request_atom("WM_DELETE_WINDOW"); atomc[1] = request_atom("WM_PROTOCOLS"); atomc[2] = request_atom("WM_NAME"); atomWmDeleteWindow = get_atom(atomc[0]); atomWmProtocols = get_atom(atomc[1]); atomWmName = get_atom(atomc[2]); } static void setup_keymap() { const xcb_setup_t *setup = xcb_get_setup(conn); xcb_get_keyboard_mapping_cookie_t kmc = xcb_get_keyboard_mapping( conn, setup->min_keycode, setup->max_keycode - setup->min_keycode + 1); keymap = xcb_get_keyboard_mapping_reply(conn, kmc, NULL); } static int state_to_plane(uint16_t state) { if (state & XCB_KEY_BUT_MASK_SHIFT) { return 1; } return 0; } static xcb_keysym_t lookup_keysym(xcb_keycode_t code, uint16_t state) { const xcb_setup_t *setup = xcb_get_setup(conn); xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(keymap); int idx; if (code > setup->max_keycode || code < setup->min_keycode) { return XCB_NO_SYMBOL; } idx = (code - setup->min_keycode) * keymap->keysyms_per_keycode + state_to_plane(state); if (idx >= keymap->length) { return XCB_NO_SYMBOL; } return keysyms[idx]; } static bool handle_user_input(struct gbs *gbs, char c) { switch (c) { case 'p': play_prev_subsong(gbs); break; case 'n': play_next_subsong(gbs); break; case 'q': case 27: quit = 1; break; case ' ': toggle_pause(); update_title(); break; case '1': case '2': case '3': case '4': gbs_toggle_mute(gbs, c-'1'); break; case 'l': gbs_cycle_loop_mode(gbs); break; default: return false; // unhandled key -> no status change } return true; } int main(int argc, char **argv) { uint32_t value_mask; uint32_t value_list[1]; xcb_generic_event_t *event; xcb_visualtype_t *visual; struct gbs *gbs; struct sigaction sa; gbs = common_init(argc, argv); metadata = gbs_get_metadata(gbs); /* init signal handlers */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = exit_handler; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGSEGV, &sa, NULL); /* init X11 */ conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(conn)) { fprintf(stderr, _("Could not connect to X server: XCB error %d\n"), xcb_connection_has_error(conn)); exit(1); } setup_atoms(); setup_keymap(); screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; visual = find_visual(screen); if (visual == NULL) { fputs(_("Could not find visual\n"), stderr); exit(1); } window = xcb_generate_id(conn); value_mask = XCB_CW_EVENT_MASK; value_list[0] = XCB_EVENT_MASK_EXPOSURE \ | XCB_EVENT_MASK_STRUCTURE_NOTIFY \ | XCB_EVENT_MASK_PROPERTY_CHANGE \ | XCB_EVENT_MASK_KEY_PRESS \ | XCB_EVENT_MASK_BUTTON_RELEASE; xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, 0, 0, window_width, window_height, 0 /* border_width */, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, value_list); xcb_map_window(conn, window); icccm_init(window); surface = cairo_xcb_surface_create(conn, window, visual, window_width, window_height); cr = cairo_create(surface); cairo_get_matrix(cr, &default_matrix); xcb_flush(conn); /* main loop */ while (!quit) { if (!step_emulation(gbs)) { quit = 1; break; } while ((event = xcb_poll_for_event(conn))) { switch (event->response_type & XCB_EVENT_MASK) { case XCB_EXPOSE: { // xcb_expose_event_t *eev = (xcb_expose_event_t *) event; screen_dirty = 1; screen_modified = 1; break; } case XCB_KEY_PRESS: { xcb_key_press_event_t *kev = (xcb_key_press_event_t *) event; xcb_keysym_t sym = lookup_keysym(kev->detail, kev->state); // fprintf(stderr, "key press (key=%d, state=%d, sym=%04x)\n", kev->detail, kev->state, sym); screen_dirty |= handle_user_input(gbs, sym); break; } case XCB_KEY_RELEASE: break; case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *bev = (xcb_button_release_event_t *) event; if (handle_button(bev, gbs)) quit = 1; break; } case XCB_PROPERTY_NOTIFY: { xcb_property_notify_event_t *pnev = (xcb_property_notify_event_t *) event; if (pnev->atom == atomWmName) { break; } // fprintf(stderr, "property notify: %s\n", debug_atom_name(pnev->atom)); break; } case XCB_CLIENT_MESSAGE: { int i; xcb_client_message_event_t *cmev = (xcb_client_message_event_t *) event; if (cmev->type == atomWmProtocols && cmev->data.data32[0] == atomWmDeleteWindow) { quit = 1; break; } fprintf(stderr, "client message: %s\n", debug_atom_name(cmev->type)); for (i=0; i < ARRAY_SIZE(cmev->data.data32); i++) { fprintf(stderr, "data%d: %08x (%s)\n", i, cmev->data.data32[i], debug_atom_name(cmev->data.data32[i])); } break; } case XCB_CONFIGURE_NOTIFY: { screen_modified = 1; screen_dirty = 1; break; } case XCB_MAP_NOTIFY: /* Ignored event */ break; default: { fprintf(stderr, _("unhandled event %d\n"), event->response_type & XCB_EVENT_MASK); break; } } free(event); xcb_flush(conn); } if (screen_modified) { xcb_get_geometry_cookie_t c = xcb_get_geometry(conn, window); xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(conn, c, NULL); window_width = r->width; window_height = r->height; free(r); cairo_xcb_surface_set_size(surface, window_width, window_height); screen_dirty = 1; screen_modified = 0; } if (is_running() && has_status_changed(gbs)) { update_title(); screen_dirty = 1; } if (screen_dirty) { redraw(); xcb_flush(conn); screen_dirty = 0; { xcb_get_geometry_cookie_t c = xcb_get_geometry(conn, window); xcb_get_geometry_reply_t *r = xcb_get_geometry_reply(conn, c, NULL); if (r->width != window_width || r->height != window_height) { screen_modified = 1; } } } } /* stop sound */ common_cleanup(gbs); /* clean up X11 stuff */ cairo_surface_finish(surface); cairo_surface_destroy(surface); xcb_disconnect(conn); return 0; } gbsplay-0.0.102/plugout_devdsp.c0000644000175100017510000000515515105657176016154 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * header file for /dev/dsp sound output plugin * * 2003-2020 (C) by Christian Garbs * Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include #include #include #include #include "common.h" #include "plugout.h" static int fd; static long devdsp_open(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata) { int c; int flags; UNUSED(metadata); if ((fd = open("/dev/dsp", O_WRONLY|O_NONBLOCK)) == -1) { fprintf(stderr, _("Could not open /dev/dsp: %s\n"), strerror(errno)); return -1; } if ((flags = fcntl(fd, F_GETFL)) == -1) { fprintf(stderr, _("fcntl(F_GETFL) failed: %s\n"), strerror(errno)); } else if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) { fprintf(stderr, _("fcntl(F_SETFL, flags&~O_NONBLOCK) failed: %s\n"), strerror(errno)); } switch (*endian) { case PLUGOUT_ENDIAN_BIG: c = AFMT_S16_BE; break; case PLUGOUT_ENDIAN_LITTLE: c = AFMT_S16_LE; break; case PLUGOUT_ENDIAN_AUTOSELECT: c = AFMT_S16_NE; break; } if ((ioctl(fd, SNDCTL_DSP_SETFMT, &c)) == -1) { fprintf(stderr, _("ioctl(fd, SNDCTL_DSP_SETFMT, %d) failed: %s\n"), c, strerror(errno)); return -1; } c = rate; if ((ioctl(fd, SNDCTL_DSP_SPEED, &c)) == -1) { fprintf(stderr, _("ioctl(fd, SNDCTL_DSP_SPEED, %ld) failed: %s\n"), rate, strerror(errno)); return -1; } if (c != rate) { fprintf(stderr, _("Requested rate %ldHz, got %dHz.\n"), rate, c); rate = c; } c=1; if ((ioctl(fd, SNDCTL_DSP_STEREO, &c)) == -1) { fprintf(stderr, _("ioctl(fd, SNDCTL_DSP_STEREO, %d) failed: %s\n"), c, strerror(errno)); return -1; } c=(4 /* max fragments */ << 16) + 11 /* selector for 2048 bytes fragment size */; if ((ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &c)) == -1) fprintf(stderr, _("ioctl(fd, SNDCTL_DSP_SETFRAGMENT, %08x) failed: %s\n"), c, strerror(errno)); /* get the actual fragment size we got */ if ((ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &c)) == -1) { fprintf(stderr, _("ioctl(fd, SNDCTL_DSP_GETBLKSIZE) failed: %s\n"), strerror(errno)); return -1; } *buffer_bytes = c; return 0; } static ssize_t devdsp_write(const void *buf, size_t count) { return write(fd, buf, count); } static void devdsp_close(void) { (void)close(fd); } const struct output_plugin plugout_devdsp = { .name = "oss", .description = "OSS sound driver", .open = devdsp_open, .write = devdsp_write, .close = devdsp_close, }; gbsplay-0.0.102/gblfsr.h0000644000175100017510000000112015105657176014360 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2003-2024 (C) by Tobias Diedrich * Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #ifndef _GBLFSR_H_ #define _GBLFSR_H_ #include #include #include "common.h" struct gblfsr { uint16_t lfsr; bool narrow; }; void gblfsr_reset(struct gblfsr* gblfsr); void gblfsr_trigger(struct gblfsr* gblfsr); void gblfsr_set_narrow(struct gblfsr* gblfsr, bool narrow); int gblfsr_next_value(struct gblfsr* gblfsr); #endif gbsplay-0.0.102/LICENCE0000644000175100017510000003053015105657176013724 0ustar runnerrunner GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! gbsplay-0.0.102/util.c0000644000175100017510000001023215105657176014055 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * This file contains various toolbox functions that * can be useful in different parts of gbsplay. * * 2003-2020 (C) by Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include "common.h" #include "util.h" #include "test.h" typedef bool emit_fn(void *ctx, uint8_t val); static int vspack(emit_fn emit, void *ctx, const char *fmt, va_list ap) { /* < little endian * > big endian * = native endian * { enter verbatim section * } exit verbatim section * b byte (8 bit) * w word (16 bit) * d doubleword (32 bit) * q quadword (64 bit) */ bool little_endian = is_le_machine(); bool verbatim = false; int bytes = 0; int written = 0; uint64_t tmp = 0; for (;*fmt;fmt++) { if (verbatim && *fmt != '}') { tmp = *fmt; bytes = 1; } else switch (*fmt) { case '{': verbatim = true; break; case '}': verbatim = false; break; case '=': little_endian = is_le_machine(); break; case '<': little_endian = true; break; case '>': little_endian = false; break; case 'b': /* promoted type is int */ tmp = va_arg(ap, int); bytes = sizeof(uint8_t); break; case 'w': /* promoted type is int */ tmp = va_arg(ap, int); bytes = sizeof(uint16_t); break; case 'd': tmp = va_arg(ap, uint32_t); bytes = sizeof(uint32_t); break; case 'q': tmp = va_arg(ap, uint64_t); bytes = sizeof(uint64_t); break; } for (int i = 0; i < bytes; i++) { int shift = little_endian ? i : bytes - i - 1; uint64_t x = tmp >> (shift * 8); if (!emit(ctx, x)) { goto exit; } written++; } bytes = 0; } exit: return written; } static bool emit_buf(void *ctx, uint8_t val) { uint8_t **buf = ctx; **buf = val; (*buf)++; return true; } static bool emit_file(void *ctx, uint8_t val) { FILE *f = ctx; return fwrite(&val, 1, 1, f) == 1; } int spack(uint8_t *dst, const char *fmt, ...) { int written; va_list ap; va_start(ap, fmt); written = vspack(emit_buf, &dst, fmt, ap); va_end(ap); return written; } int fpack(FILE *f, const char *fmt, ...) { int written; va_list ap; va_start(ap, fmt); written = vspack(emit_file, f, fmt, ap); va_end(ap); return written; } int fpackat(FILE *f, long offset, const char *fmt, ...) { int written; va_list ap; fseek(f, offset, SEEK_SET); va_start(ap, fmt); written = vspack(emit_file, f, fmt, ap); va_end(ap); return written; } test void test_spack(void) { uint8_t want[] = { 0x20, 1, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0x31, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4 }; uint8_t got[sizeof(want)] = { 0 }; uint8_t b = 1; uint16_t w = 2; uint32_t d = 3; uint64_t q = 4; int l = spack(got, "{ }bwdq", b, w, d, q, b, w, d, q); ASSERT_ARRAY_EQUAL("%2d", want, got); ASSERT_EQUAL("%d", (int)sizeof(want), l); } TEST(test_spack); static uint64_t rand_state = 88172645463325252ULL; uint64_t xorshift64(uint64_t *state) { /* Algorithm "xor64" from p. 4 of Marsaglia, "Xorshift RNGs" */ uint64_t x = *state; x ^= x << 13; x ^= x >> 7; x ^= x << 17; *state = x; return x; } void rand_seed(uint64_t seed) { rand_state = seed + 88172645463325252ULL; } long rand_long(long max) /* return random long from [0;max[ */ { /* This is not thread-safe, but rand_long and shuffle_long are only used in * player.c so there is no need for thread-safety at this point. */ uint64_t r = xorshift64(&rand_state); return (long)(r % max); } void shuffle_long(long *array, long elements) /* shuffle a long array in place * Fisher-Yates algorithm, see `perldoc -q shuffle` :-) */ { long i, j, temp; for (i = elements-1; i > 0; i--) { j=rand_long(i); /* pick element */ temp = array[i]; /* swap elements */ array[i] = array[j]; array[j] = temp; } } test void test_shuffle(void) { long actual[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; long expected[] = { 6, 8, 2, 7, 3, 4, 5, 9, 1 }; long len = sizeof(actual) / sizeof(*actual); rand_seed(0); shuffle_long(actual, len); ASSERT_ARRAY_EQUAL("%ld", actual, expected); } TEST(test_shuffle); TEST_EOF; gbsplay-0.0.102/plugout.h0000644000175100017510000000435615105657176014616 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2004-2021 (C) by Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #ifndef _PLUGOUT_H_ #define _PLUGOUT_H_ #include #include #include "config.h" #include "common.h" #include "gbhw.h" #if PLUGOUT_DSOUND == 1 # define PLUGOUT_DEFAULT "dsound" #elif PLUGOUT_PIPEWIRE == 1 # define PLUGOUT_DEFAULT "pipewire" #elif PLUGOUT_PULSE == 1 # define PLUGOUT_DEFAULT "pulse" #elif PLUGOUT_ALSA == 1 # define PLUGOUT_DEFAULT "alsa" #elif PLUGOUT_SDL == 1 # define PLUGOUT_DEFAULT "sdl" #else # define PLUGOUT_DEFAULT "oss" #endif enum plugout_endian { PLUGOUT_ENDIAN_BIG, PLUGOUT_ENDIAN_LITTLE, PLUGOUT_ENDIAN_AUTOSELECT, }; #if GBS_BYTE_ORDER == GBS_ORDER_LITTLE_ENDIAN #define PLUGOUT_ENDIAN_NATIVE PLUGOUT_ENDIAN_LITTLE #else #define PLUGOUT_ENDIAN_NATIVE PLUGOUT_ENDIAN_BIG #endif struct plugout_metadata { const char * player_name; const char * filename; }; /* Initial open of plugout. */ typedef long (*plugout_open_fn )(enum plugout_endian *endian, long rate, long *buffer_bytes, const struct plugout_metadata metadata); /* Notification when next subsong is about to start. */ typedef int (*plugout_skip_fn )(int subsong); /* Notification the the player is paused/resumed. */ typedef void (*plugout_pause_fn)(int pause); /* Callback for monitoring IO in dumpers. Cycles restarts at 0 when the subsong is changed. */ typedef int (*plugout_io_fn )(cycles_t cycles, uint32_t addr, uint8_t val); /* Callback for monitoring inferred channel status. */ typedef int (*plugout_step_fn )(const cycles_t cycles, const struct gbs_channel_status[]); /* Callback for writing sample data. */ typedef ssize_t (*plugout_write_fn)(const void *buf, size_t count); /* Close called on player exit. */ typedef void (*plugout_close_fn)(void); #define PLUGOUT_USES_STDOUT 1 struct output_plugin { char *name; char *description; long flags; plugout_open_fn open; plugout_skip_fn skip; plugout_pause_fn pause; plugout_io_fn io; plugout_step_fn step; plugout_write_fn write; plugout_close_fn close; }; void plugout_list_plugins(void); const struct output_plugin* plugout_select_by_name(const char* const name); #endif gbsplay-0.0.102/terminal_posix.c0000644000175100017510000000262615105657176016145 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2020 (C) by Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include #include "common.h" #include "terminal.h" static long terminit; static struct termios ots; void exit_handler(int signum) { printf(_("\nCaught signal %d, exiting...\n"), signum); restore_terminal(); exit(1); } void stop_handler(int signum) { restore_terminal(); } void cont_handler(int signum) { setup_terminal(); redraw = true; } static void setup_handlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = exit_handler; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGSEGV, &sa, NULL); sa.sa_handler = stop_handler; sigaction(SIGSTOP, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sa.sa_handler = cont_handler; sigaction(SIGCONT, &sa, NULL); } void setup_terminal(void) { struct termios ts; setup_handlers(); if (tcgetattr(STDIN_FILENO, &ts) == -1) return; ots = ts; ts.c_lflag &= ~(ICANON | ECHO | ECHONL); tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts); fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); terminit = 1; } void restore_terminal(void) { if (terminit) tcsetattr(STDIN_FILENO, TCSAFLUSH, &ots); } long get_input(char *c) { return read(STDIN_FILENO, c, 1) == 1; } gbsplay-0.0.102/gbs2gb.c0000644000175100017510000000460415105657176014254 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2003-2025 (C) by Tobias Diedrich * Christian Garbs * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include "common.h" #include "gbhw.h" #include "gbcpu.h" #include "libgbs.h" #include "gbs_internal.h" /* global variables */ uint8_t logo_data[0x30]; void usage(long exitcode) { FILE *out = exitcode ? stderr : stdout; fputs(_("Usage: gbs2gb [OPTION]... [--] GBS-FILE OUT-FILE\n" "\n" "Available options are:\n" " -t rom template\n" " -h display this help and exit\n" " -V print version and exit\n" " -- end options, next argument is GBS-FILE\n"), out); exit(exitcode); } void version(void) { (void)puts("gbs2gb " GBS_VERSION); exit(EXIT_SUCCESS); } void read_rom_template(const char* const name) { FILE *f = fopen(name, "rb"); uint8_t hdr[0x200]; if (!f) { fprintf(stderr, _("Could not open ROM template: %s"), name); exit(EXIT_FAILURE); } if (fread(hdr, 1, sizeof(hdr), f) != sizeof(hdr)) { fclose(f); fprintf(stderr, _("Could not open ROM template: %s"), name); exit(EXIT_FAILURE); } memcpy(logo_data, &hdr[0x104], 0x30); fclose(f); } void parseopts(int *argc, char ***argv) { long res; while ((res = getopt(*argc, *argv, "t:hV")) != -1) { switch (res) { default: usage(EXIT_FAILURE); break; case 't': read_rom_template(optarg); break; case 'h': usage(EXIT_SUCCESS); break; case 'V': version(); break; } } *argc -= optind; *argv += optind; } void read_default_template(void) { const uint8_t *bootrom = gbs_internal_api.get_bootrom(); if (!bootrom) { return; } memcpy(logo_data, &bootrom[0xa8], 0x30); } int main(int argc, char **argv) { struct gbs *gbs; FILE *out; assert_internal_api_valid(); i18n_init(); read_default_template(); parseopts(&argc, &argv); if (argc < 2) { usage(EXIT_FAILURE); } if (logo_data[0] == 0) { fputs(_("ROM template data not found!\n"), stderr); usage(EXIT_FAILURE); } if ((gbs = gbs_open(argv[0])) == NULL) exit(EXIT_FAILURE); out = fopen(argv[1], "wb"); if (!out) { fprintf(stderr, _("Could not open output file: %s"), argv[1]); exit(EXIT_FAILURE); } gbs_internal_api.write_rom(gbs, out, logo_data); gbs_close(gbs); fclose(out); return 0; } gbsplay-0.0.102/gen_impulse_h.c0000644000175100017510000000231115105657176015715 0ustar runnerrunner/* * gbsplay is a Gameboy sound player * * 2003-2020 (C) by Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include "impulsegen.h" #define IMPULSE_N_SHIFT 7 /* 128 shifted impulses */ #define IMPULSE_W_SHIFT 5 /* 32 samples per impulse */ #define IMPULSE_CUTOFF 1.0 /* Cutoff at nyquist limit (no cutoff) */ #define IMPULSE_WIDTH (1 << IMPULSE_W_SHIFT) #define IMPULSE_W_MASK (IMPULSE_WIDTH - 1) #define IMPULSE_N (1 << IMPULSE_N_SHIFT) #define IMPULSE_N_MASK (IMPULSE_N - 1) int main(int argc, char **argv) { int32_t *impulsetab = gen_impulsetab(IMPULSE_W_SHIFT, IMPULSE_N_SHIFT, IMPULSE_CUTOFF); int n = (1 << IMPULSE_N_SHIFT) * (1 << IMPULSE_W_SHIFT); int i; if (impulsetab == NULL) { fprintf(stderr, "Failed to generate impulse table."); return 1; } printf("#define IMPULSE_N_SHIFT %d\n", IMPULSE_N_SHIFT); printf("#define IMPULSE_W_SHIFT %d\n", IMPULSE_W_SHIFT); printf("static const int32_t base_impulse[] = {"); for (i=0; i * Tobias Diedrich * * Licensed under GNU GPL v1 or, at your option, any later version. */ #include #include #include #include #include