首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Sinclair谱分接文件翻车机

Sinclair谱分接文件翻车机
EN

Code Review用户
提问于 2020-12-17 17:48:54
回答 2查看 434关注 0票数 8

有一天,我有机会研究一下为一台Z80计算机编写的一个古老的辛克莱谱软件。这台机器的软件通常被保存到录音带中,现在人们使用抽头格式存储和交换频谱软件。该格式是原始磁带格式的直译,由一系列块组成(此处呈现为TAPBlock类)。典型的格式是有一个头(在这段代码中是TAPHeader),后面是一个代码块。1984年的巫师巢穴游戏就是一个例子。

的目的

这个软件的目的是简单地读取文件并将内容转储成某种结构化的、人类可读的形式。我最感兴趣的是查看十六进制字节字符串(这就是我家里的“人类可读性”!)这就是我在这里使用的格式。一切都按计划进行。下面显示了上述游戏文件的部分示例输出。

代码语言:javascript
复制
{ len = 19, flag = 0, {
        00 77 6c 20 20 20 20 20 20 20 20 c7 3e 02 00 c7 
        3e 
}, cksum = 25, calcsum = 25 }
{ type = 0, filename = "wl        ", data_len = 16071, param1 = 2, param2 = 16071 }
{ len = 16073, flag = 255, {
        00 01 0a 00 ec 31 30 0e 00 00 0a 00 00 0d 00 02 
        0d 00 fd 34 39 39 39 39 0e 00 00 4f c3 00 0d 00 
        ...
        33 36 37 36 0e 00 00 7c 5c 00 2c 31 39 39 0e 00 
        00 c7 00 00 3a fe 0d 
}, cksum = 122, calcsum = 122 }
{ len = 19, flag = 0, {
        03 77 6c 7a 20 20 20 20 20 20 20 38 07 50 c3 00 
        80 
}, cksum = 110, calcsum = 110 }
{ type = 3, filename = "wlz       ", data_len = 1848, param1 = 50000, param2 = 32768 }
{ len = 1850, flag = 255, {
        00 00 00 00 18 3c 7e 7e 7e 5a 7e 3c 24 3c 3c 18 
        00 03 07 01 06 06 0f 07 e7 f7 ff ff ff 9f 0f 88 
        ...
        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
        00 00 00 00 00 00 00 00 
}, cksum = 209, calcsum = 209 }
{ len = 19, flag = 0, {
        03 77 6c 62 20 20 20 20 20 20 20 6e 02 60 ea c3 
        80 
}, cksum = 255, calcsum = 255 }
{ type = 3, filename = "wlb       ", data_len = 622, param1 = 60000, param2 = 32963 }
{ len = 624, flag = 255, {
        c0 38 00 3d 0e 72 0e 71 1c 43 1c 48 2f c0 43 1c 
        43 0e c0 72 0e 72 08 79 08 77 21 71 21 71 1c c0 
        ...
        bf 94 c0 bf 8f b8 8d b0 8f a9 8e c0 ad 8a bf 89 
        ff 50 35 0a 5a 38 64 5a a8 7a 5a c8 0c ff 
}, cksum = 170, calcsum = 170 }
{ len = 19, flag = 0, {
        03 77 6c 62 62 20 20 20 20 20 20 77 01 00 fa 80 
        80 
}, cksum = 148, calcsum = 148 }
{ type = 3, filename = "wlbb      ", data_len = 375, param1 = 64000, param2 = 32896 }
{ len = 377, flag = 255, {
        d9 e1 d9 c9 d9 e5 d9 21 34 eb 7e fe ff 28 f1 fe 
        c0 20 0b 23 7e 32 c9 fa 23 7e 32 ca fa 23 7e 32 
        ...
        44 42 00 00 3c 40 3c 02 42 3c 00 00 fe 10 10 10 
        10 10 2a b2 5c ed 5b 
}, cksum = 176, calcsum = 176 }

我的问题

我不太喜欢我创建TAPHeader构造函数的方式。我考虑过用static_cast<TAPHeader>(data.data())做一些更丑陋的事情,但说得越少越好。有更好的方法吗?关于如何改进代码的任何其他评论也是受欢迎的。我正在为此使用C++17,但是如果C++20提供了一些有吸引力的特性,在这里也是有用的,我也不会反对它。

tapdump.cpp

代码语言:javascript
复制
#include <iostream>
#include <iterator>
#include <iomanip>
#include <fstream>
#include <functional>
#include <numeric>
#include <cstdint>
#include <vector>
#include <optional>

struct TAPHeader {
    uint8_t type;
    uint8_t filename[11];  // actually 10 bytes, but we add NUL terminator
    uint16_t data_len;
    uint16_t param1;
    uint16_t param2;

    explicit TAPHeader(const std::vector<uint8_t> v) {
        auto it = v.begin();
        type = *it++;
        for (int i=0; i<10; ++i) {
            filename[i] = *it++;
        }
        filename[10] = '\0';  // terminate filename string
        data_len = *it++;
        data_len |= *it++ << 8;
        param1 = *it++;
        param1 |= *it++ << 8;
        param2 = *it++;
        param2 |= *it++ << 8;
    }

    friend std::ostream& operator<<(std::ostream& out, const TAPHeader& hdr) {
        return out 
            << "{ type = " << (unsigned)hdr.type
            << ", filename = \"" << hdr.filename
            << "\", data_len = " << hdr.data_len
            << ", param1 = " << hdr.param1
            << ", param2 = " << hdr.param2
            << " }";
    }
};

class TAPBlock {
    uint16_t len;   // little-endian
    uint8_t flag;  // 0 = header, 0xff = body        
    uint8_t cksum; // simple xor of data, excluding len
    std::vector<uint8_t> data;
public:
    uint8_t calcsum() const {
        return std::accumulate(data.begin(), data.end(), flag, std::bit_xor<uint8_t>());
    }
    bool is_ok() const { return calcsum() == cksum; }

    std::optional<TAPHeader> get_header() const {
        if (flag == 0 && data.size() >= 17) {
            return TAPHeader{data};
        }
        return {};
    }


    bool read(std::istream& in) {
        uint8_t lo, hi;
        lo = in.get();
        hi = in.get();
        len = (hi << 8) | lo;
        flag = in.get();
        data.clear();
        data.reserve(len-2);
        for (auto count{len-2}; count; --count) {
            data.push_back(in.get());
        }
        cksum = in.get();
        return in.good();
    }

    friend std::ostream& operator<<(std::ostream& out, const TAPBlock blk) {
        out << "{ len = " << std::dec << blk.len << ", flag = " << (unsigned)blk.flag << ", {";
        if (1 || blk.flag) {
            int remaining{0};
            for (auto n : blk.data) {
                if (remaining == 0) {
                    remaining = 16;
                    out << "\n\t";
                }
                --remaining;
                out << std::setw(2) << std::setfill('0') << std::hex << (unsigned)n << ' ';
            }
            out << "\n}, ";
        } else {

        }
        return out << "cksum = " << std::dec << (unsigned)blk.cksum
            << ", calcsum = " << std::dec << (unsigned)blk.calcsum() << " }";
    }
};

int main(int argc, char *argv[])
{
    if (argc != 2) {
        std::cout << "Usage: dumptap tapfilename\n";
        return 1;
    }
    TAPBlock blk;
    std::ifstream in{argv[1]};
    while (blk.read(in)) {
        std::cout << blk << '\n';
        if (auto hdr = blk.get_header()) {
            std::cout << *hdr << '\n';
        }
    }
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2020-12-18 07:52:00

  • 虽然这会使代码变得更长一些,但我想我应该从按照以下一般顺序编写一个"buffer“类开始:
代码语言:javascript
复制
class buffer { 
    std::vector<uint8_t> &data;
    std::vector<uint8_t>::iterator pos;
public:
    buffer(std::vector<uint8_t> &data) 
        : data(data)
        , pos(data.cbegin()) 
    {}

    void read(char *dest, size_t len) {
        std::copy_n(pos, dest, len);        
        pos += len;
    }

    void read(uint16_t &dest) {
        dest = *pos++;
        dest |= *pos++ << 8;
    }
};

使用此方法,ctor for TAPHeader的结果如下所示:

代码语言:javascript
复制
    explicit TAPHeader(std::vector<uint8_t> const &data, TAPHeader &hdr) {
        buffer b(data);
        b.read(&type, sizeof(type));
        b.read(filename, sizeof(filename)-1);
        filename[10] = '\0';
        b.read(data_len);
        b.read(param1);
        b.read(param2);
    }

我发现这足够简洁和简单,足以证明buffer的代码是正确的。

  • 对于flag的TAPBlock成员,我可能会这样做:
代码语言:javascript
复制
enum Flag : uint8_t { header = 0, body = 0xff } flag;

那么get_header可能会出现这样的情况:

代码语言:javascript
复制
    std::optional<TAPHeader> get_header() const {
        const int min_header_size = 17;

        if (flag == header && data.size() >= min_header_size) {
            return TAPHeader{data};
        }
        return {};
    }

至少对我来说,这更清楚地表达了意图。

  • 现在,您打印出原始校验和(作为读取和计算),并将其留给读者来验证它们是否匹配。就我个人而言,我更愿意让计算机来做这个检查(如果它们匹配的话,可以考虑完全忽略消息,如果它们不匹配,可以添加ANSI代码以亮红色打印出来)。
票数 5
EN

Code Review用户

发布于 2020-12-17 20:48:29

TAPHeader构造函数和TAPBlock::operator<<的参数可以作为const引用传递,以避免复制传递的对象。

代码语言:javascript
复制
explicit TAPHeader(const std::vector<uint8_t> &v);
// ...
friend std::ostream& operator<<(std::ostream& out, const TAPBlock &blk);

TAPHeader构造函数中,在读取字节时计算两个字节长度值,但在TAPBlock::read中,将两个字节读入局部变量,然后使用这些字节计算长度。为了保持一致性,您应该对两者使用相同的样式。可以将read更改为启动:

代码语言:javascript
复制
len = in.get();
len |= in.get() << 8;

那么您就不需要lohi变量了。

票数 6
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/253585

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档