Yuu

前言

既然本篇文的题目是古早味软件合成器,那么必定不会涉及到VSTi等现代插件技术来实现的软件合成器,取而代之的则是Windows9x系统中常见的“虚拟设备驱动程序"(VxD),其对环境的限制也更加苛刻,运行起来也需要更多的准备。
在本文中,我将使用卡西欧于1996年所发布的SW-10软件合成器作为范例进行演示。
CASIO SW-10合成器是附带于CASIO LANA Lite卡拉OK软件(在互联网速度不太优秀的时代,基于MIDI的卡拉OK软件和网站非常的多)之中的赠品,所以自然而然,合成器部分的获取也是免费的,刚好符合了本文的标题“免费”和“古早味”这两个关键字。

环境准备

需要一个Windows98或者Me的虚拟机或物理机
如果你使用虚拟机,目前仅可以使用VirtualBox
系统的安装过程在此不再赘述,推荐安装英文版或者日文版,避免遇到内码错误问题。
VTGSWS_Windows_Prepared

资源准备

由于源程序发布于1997年,其官网链接也早已不存,我们需要使用网页档案馆来获取安装包
LanaSw10.exe
在下载之后,可能会想到直接安装,但是依据个人经验,在非ShiftJIS内码的古早系统上直接安装日文程序,绝不是一个安全的事情。
所以我们选择直接解压其中的文件来获取到VxD驱动安装包
第一步,我们需要尝试知道一下LanaSw10.exe的格式
通过WinRAR方便的拓展,则立即知道了这是一个LZH压缩包
VTGSWS_LANA_Prop
在使用WinRAR解压之后,则得到了一堆文件,在其之中找到最大的文件以供解压
VTGSWS_LANA_3Z
但是直接解压肯定是报错的,所以在互联网尝试搜索,找到了大佬对卡西欧SW-10的全面逆向
M-HT/casio_sw-10
可以使用其中的zextractfile来解压出文件,但是其需要文件名才能解压
历尽一些简单的分析,发现这个Z文件将文件名存于尾部
通过cat指令则得出其中包括有以下文件
VTGSWS_Extract_File_Name
解压后得到
VTGSWS_Extracted

安装步骤

现在我们已经得到了文件,所以接下来的步骤十分简单
只需要进入Windows的控制面板,找到"Add New Hardware"
VTGSWS_ControlPanel
点选添加"Sound, Video and Game controllers"
VTGSWS_DeviceType
点选"Have Disk"
VTGSWS_HaveDisk
选择"OEMSETUP.INF"其后会出现设备选择框,选择"CASIO SW-10"
VTGSWS_ChooseSW10
之后则会弹出卡西欧的配置面板
VTGSWS_SW10Config
重启之后就可以使用任意支持MIDI播放的播放器(比如Windows Media Player)播放你想要听的MIDI文件了


概述

很久很久以前(大约20年7月)写的一个奇奇怪怪的算法,当时的目的是为了从一堆动漫图片中获取其线稿用于描画练习
但是当时还没有现如今流行的各种大模型,根本做不到快速的本地抽出,所以就去尝试了解了一下一些简单的图像处理库
(当时还在用7300HQ老U,做不到基于纯图形或者推测的处理),后来就想了个办法,用色容差和反色+灰度来解决这个问题,出来的效果也暂且不算难看,用于描画练习则是绰绰有余了。
最近翻出来了当时的V1和V2版本代码,仔细阅读后发现写的属实非常烂,就直接尝试对其进行了直接的整体重构
以下为代码,编译时请携带好STB的头,用clang -Os -o OSE3 OSE3.C -lm来执行编译

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#define GRAYSCALE_RED_WEIGHT 0.3
#define GRAYSCALE_GREEN_WEIGHT 0.59
#define GRAYSCALE_BLUE_WEIGHT 0.11

void imgProcessGray(uint8_t *s, int sx, int sy, int stride, uint8_t *p, int gstride)
{
    for (int y=0; y<sy; y++) {
        for (int x=0; x<sx; x++) {
            p[x] = GRAYSCALE_RED_WEIGHT * s[x*3+0] + GRAYSCALE_GREEN_WEIGHT * s[x*3+1] + GRAYSCALE_BLUE_WEIGHT *s[x*3+2];
        }
        s += stride*3;
        p += gstride;
    }
}

void imgProcessDilate(uint8_t *s, int w, int h, int k, uint8_t *p)
{
    for (int y=1; y<h-1; y++) {
        for (int x=1; x<w-1; x++) {
            uint8_t uc = s[ (y+0)*w + (x+0) ];
            uint8_t ua = s[ (y-1)*w + (x+0) ];
            uint8_t ub = s[ (y+1)*w + (x+0) ];
            uint8_t ul = s[ (y+0)*w + (x-1) ];
            uint8_t ur = s[ (y+0)*w + (x+1) ];
            uint8_t ux = 0;
            if (uc > ux) ux = uc;
            if (ua > ux) ux = ua;
            if (ub > ux) ux = ub;
            if (ul > ux) ux = ul;
            if (ur > ux) ux = ur;
            p[ y*w + x ] = ux;
        }
    }
}

void imgProcessAbsdiff(uint8_t *s, uint8_t *s2, int w, int h, uint8_t *p)
{
    for (int n=0; n<w*h; n++) {
        *p++ = abs(*s++ - *s2++);
    }
}

void imgProcessReverse(uint8_t *s, int w, int h, uint8_t *p)
{
    for (int n=0; n<w*h; n++) {
        *p++ = 255 - *s++;
    }
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Otaku Strokes Extractor v3\n");
        fprintf(stderr, "Usage: %s <image_path>\n", argv[0]);
        return 1;
    }

    char *name = argv[1];

    int w, h, bpp;
    uint8_t *pixels = stbi_load(name, &w, &h, &bpp, 3);
    if (!pixels) {
        fprintf(stderr, "Failed to load image: %s\n", name);
        return 1;
    }

    uint8_t *gray = (uint8_t*) malloc(w*h*4);
    if (!gray) {
        fprintf(stderr, "Failed to allocate memory for image processing.\n");
        stbi_image_free(pixels);
        return 1;
    }

    uint8_t *dilated = gray + w*h;
    uint8_t *diff = dilated + w*h;
    uint8_t *contour = diff + w*h;

    printf("Processing %s...\n", name);
    imgProcessGray(pixels, w, h, w, gray, w);
    imgProcessDilate(gray, w, h, 5, dilated);
    imgProcessAbsdiff(gray, dilated, w, h, diff);
    imgProcessReverse(diff, w, h, contour);
    stbi_write_jpg("result.jpg", w, h, 1, contour, 0);
    printf("Done for %s !\n", name);

    free(gray);
    stbi_image_free(pixels);

    return 0;
}

Windows体验版(使用MSYS2的gcc 11.3.0链静态构建)
OSE3_x64.exe

效果

原图
OSE3_Original
线稿
OSE3_Result
©版权所有者:クール教信者·双葉社/ドラゴン生活向上委員会


利用OpenCV的色容差检测功能,做到了马来西亚国旗——辉煌条纹(Jalur Gemilang)的检测
当前功能仅有检测到Jalur Gemilang时,冻结那一帧,之后可能会再添加一些2333的功能。
由于该程序目标为马来西亚人,所以提示语也采用了马来文🤣

实验效果

OPENCV_PJG

# pjg.py V3
import cv2
import numpy as np

lower_red1 = np.array([0, 100, 100])
upper_red1 = np.array([10, 255, 255])

lower_red2 = np.array([160, 100, 100])
upper_red2 = np.array([180, 255, 255])

lower_white = np.array([0, 0, 200])
upper_white = np.array([180, 30, 255])

lower_yellow = np.array([20, 100, 100])
upper_yellow = np.array([30, 255, 255])

lower_blue = np.array([100, 100, 100])
upper_blue = np.array([130, 255, 255])


def dF(frame):
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    mask_red = cv2.inRange(hsv, lower_red1, upper_red1)
    mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask_white = cv2.inRange(hsv, lower_white, upper_white)
    mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
    mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
    mask = cv2.bitwise_or(mask_red, mask_red2, mask_white)
    mask = cv2.bitwise_or(mask, mask_blue)
    mask = cv2.bitwise_or(mask, mask_yellow)
    if cv2.countNonZero(mask_red) > 100 and cv2.countNonZero(mask_white) > 100 and cv2.countNonZero(mask_blue) > 100 and cv2.countNonZero(mask_yellow) > 100:
        return True


cap = cv2.VideoCapture(0)
print("Pengesan Jalur Gemilang\nVersi Aplikasi: V3\nMasukkan 'q' untuk keluar\n")
while True:
    ret, frame = cap.read()
    if dF(frame):
        print('Unsur Jalur Gemilang dikesankan!!!')
        cv2.waitKey(0)

    cv2.imshow('Pengesan Jalur Gemilang', frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

    if cv2.getWindowProperty('Pengesan Jalur Gemilang', cv2.WND_PROP_VISIBLE) < 1:
        cap.release()
        cv2.destroyAllWindows()
        break

很久以前接触到日系手机内核里面各式各样的LSM的时候,就对LSM背后到底是什么,产生了一些奇怪的兴趣。
最近有些得空,读了一些关于LSM,钩子,内核相关的东西,再搭上以前对FJSEC这些OEM LSM的逆向的经验。
尝试胡乱弄个LSM出来看看?也许有用也也许没用,又成为了一个慢慢挖坑再填坑的作品。
为啥叫NGLSM?NG是我很早用的一个网名,全称是Nango,不懂当初为什么会想到这个名字,但是既然想到了,那么就这样叫吧。

// 2023.06.08-00.00.01/FIRST RELEASE
// 2023.06.11-00.01.01/ADD PROTECT OF SETSOCKOPT/GETSOCKOPT
#include <linux/lsm_hooks.h>
#include <linux/path.h>
#include <linux/cred.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/mount.h>
#include <linux/bprm.h>
#include <linux/socket.h>
#include <asm/uaccess.h>
#include <linux/seq_file.h>

#define STATUS_FILE "/tmp/lsm_status"
#define STATUS_PERMISSIVE "PER"
#define STATUS_ENFORCING "ENF"
#define STATUS_ENFORCING_MMAP "ENF2"
#define STATUS_MAX 100

static int nglsm_status(void)
{
    struct file *f;
    char buf[STATUS_MAX];
    mm_segment_t fs;
    int status = -1; // unknown

    f = filp_open(STATUS_FILE, O_RDONLY, 0);
    if (IS_ERR(f))
    {
        return status;
    }

    fs = get_fs();
    set_fs(get_ds());
    f->f_op->read(f, buf, STATUS_MAX, &f->f_pos);
    set_fs(fs);

    if (strncmp(buf, STATUS_PERMISSIVE, strlen(STATUS_PERMISSIVE)) == 0)
    {
        status = 0; // permissive
    }
    else if (strncmp(buf, STATUS_ENFORCING, strlen(STATUS_ENFORCING)) == 0)
    {
        status = 1; // enforcing
    }
    else if (strncmp(buf, STATUS_ENFORCING_MMAP, strlen(STATUS_ENFORCING_MMAP)) == 0)
    {
        status = 2; // enforcing mmap
    }

    filp_close(f, NULL);
    return status;
}

static int nglsm_sb_mount(const char *dev_name, struct path *path,
                          const char *type, unsigned long flags, void *data)
{
    int status = nglsm_status();
    if (status == 0)
    {
        return 0;
    }

    printk(KERN_ERR "[NGLSM]Mount was not allowed\n");
    return -EPERM;
}

static int nglsm_sb_umount(struct vfsmount *mnt, int flags)
{
    int status = nglsm_status();
    if (status == 0)
    {
        return 0;
    }

    printk(KERN_ERR "[NGLSM]UMount was not allowed\n");
    return -EPERM;
}

static int nglsm_sb_pivotroot(struct path *old_path, struct path *new_path)
{
    int status = nglsm_status();
    if (status == 0)
    {
        return 0;
    }

    printk(KERN_ERR "[NGLSM]PivotRoot was not allowed\n");
    return -EPERM;
}

static int nglsm_socket_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
{
    int status = nglsm_status();
    if (status == 0)
    {
        return 0;
    }

    printk(KERN_ERR "[NGLSM]GetSockOpt was not allowed\n");
    return -EPERM;
}

static int nglsm_socket_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
{
    int status = nglsm_status();
    if (status == 0)
    {
        return 0;
    }

    printk(KERN_ERR "[NGLSM]SetSockOpt was not allowed\n");
    return -EPERM;
}

static struct security_hook_list nglsm_hooks[] __lsm_ro_after_init = {
    LSM_HOOK_INIT(sb_mount, nglsm_sb_mount),
    LSM_HOOK_INIT(sb_umount, nglsm_sb_umount),
    LSM_HOOK_INIT(sb_pivotroot, nglsm_sb_pivotroot),
    LSM_HOOK_INIT(socket_getsockopt, nglsm_socket_getsockopt),
    LSM_HOOK_INIT(socket_setsockopt, nglsm_socket_setsockopt),
};

static int lsm_status_proc_show(struct seq_file *m, void *v)
{
    int status = nglsm_status();
    switch (status)
    {
    case 0:
        seq_printf(m, "Permissive\n");
        break;
    case 1:
        seq_printf(m, "Enforcing\n");
        break;
    case 2:
        seq_printf(m, "Enforcing Stage 2\n");
        break;
    default:
        seq_printf(m, "Unknown\n");
    }
    return 0;
}

static int lsm_status_proc_open(struct inode *inode, struct file *file)
{
    return single_open(file, lsm_status_proc_show, NULL);
}

static const struct file_operations lsm_status_proc_fops = {
    .owner = THIS_MODULE,
    .open = lsm_status_proc_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};

static __init int nglsm_init(void)
{
    security_add_hooks(nglsm_hooks, ARRAY_SIZE(nglsm_hooks), "nglsm");
    proc_create("lsm_status", 0, NULL, &lsm_status_proc_fops);
    printk(KERN_INFO "[NGLSM]LOADED\n");
    return 0;
}

security_initcall(nglsm_init);

AWA.SH

此脚本可在Alpine Linux下快速部署网页应用所需的服务器及服务

功能

  • 可选择使用的语言,包括PHP, Python, Node.js
  • 可选择网页服务器,包括Nginx, Apache, Lighttpd
  • 可选择数据库引擎,包括MariaDB, SQLite
  • 可从软件源内自动寻找最新的PHP版本并执行安装

使用

wget --no-check-certificate https://obj.yuu.ink/script/awa.sh && chmod +x awa.sh && ./awa.sh

更新

R2(2023.07.14)
修复一个严重错误
R1(2023.06.02)
初版